/*===================================================================
 *
 *                    Linux MegaRAID device driver
 *
 * Copyright 2001  LSI Logic Corporation.
 *
 *              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 of the License, or (at your option) any later version.
 *
 * Copyright (c) 2002  Red Hat, Inc. All rights reserved.
 *              - fixes
 *              - speed-ups (list handling fixes, issued_list, optimizations.)
 *              - lots of cleanups.
 *
 * Version : v1.19.6 (Feb 06, 2003)
 *
 * Description: Linux device driver for LSI Logic MegaRAID controller
 *
 * Supported controllers: MegaRAID 418, 428, 438, 466, 762, 467, 471, 490
 *                                      493.
 *
 * For history of changes, see ChangeLog.megaraid.
 *
 *===================================================================*/

#include <linux/mm.h>
#include <linux/fs.h>
#include <linux/blk.h>
#include <asm/uaccess.h>
#include <linux/delay.h>
#include <linux/reboot.h>
#include <linux/module.h>

#include "sd.h"
#include "scsi.h"
#include "hosts.h"

#include "megaraid.h"

/*
 *	Linux 2.4 and higher
 *
 *	per-controller private lock
 */

MODULE_AUTHOR ("LSI Logic Corporation");
MODULE_DESCRIPTION ("LSI Logic MegaRAID driver");
MODULE_LICENSE("GPL");

#define RDINDOOR(adapter)			readl((adapter)->base + 0x20)
#define RDOUTDOOR(adapter)			readl((adapter)->base + 0x2C)
#define WRINDOOR(adapter,value)		writel(value, (adapter)->base + 0x20)
#define WROUTDOOR(adapter,value)	writel(value, (adapter)->base + 0x2C)

#ifdef CONFIG_PROC_FS
#define COPY_BACK if (offset > adapter->procidx) { \
		*eof = TRUE; \
        adapter->procidx = 0; \
        adapter->procbuf[0] = 0; \
        return 0;} \
 if ((count + offset) > adapter->procidx) { \
      count = adapter->procidx - offset; \
      *eof = TRUE; } \
      memcpy(page, &adapter->procbuf[offset], count); \
      adapter->procidx = 0; \
      adapter->procbuf[0] = 0;
#endif

/*
 * ================================================================
 *                    Global variables
 *================================================================
 */

/*  Use "megaraid=skipXX" as LILO option to prohibit driver from scanning
    XX scsi id on each channel.  Used for Madrona motherboard, where SAF_TE
    processor id cannot be scanned */

static char *megaraid;
static int skip_id = -1;

static int numCtlrs;
static adapter_t *megaCtlrs[FC_MAX_CHANNELS];
static struct proc_dir_entry *mega_proc_dir_entry;

static struct notifier_block mega_notifier = {
	notifier_call: megaraid_reboot_notify
};

/* For controller re-ordering */
static struct mega_hbas mega_hbas[MAX_CONTROLLERS];

/*
 * The File Operations structure for the serial/ioctl interface of the driver
 */
/* For controller re-ordering */ 

static struct file_operations megadev_fops = {
	ioctl:megadev_ioctl_entry,
	open:megadev_open,
	release:megadev_close,
};

/*
 * Array to structures for storing the information about the controllers. This
 * information is sent to the user level applications, when they do an ioctl
 * for this information.
 */
static struct mcontroller mcontroller[MAX_CONTROLLERS];

/* The current driver version */
static u32 driver_ver = 118;

/* major number used by the device for character interface */
static int major;

static struct semaphore mimd_ioctl_sem, mimd_entry_mtx;

#define	IS_RAID_CH(this, ch)	((this->mega_ch_class >> (ch)) & 0x01)

/*-------------------------------------------------------------------------
 *
 *                      Local functions
 *
 *-------------------------------------------------------------------------*/

/*=======================
 * Free a SCB structure
 *=======================
 */
static void mega_freeSCB (adapter_t *adapter, scb_t *scb)
{
	if (!scb || (scb->idx >= 0xFE))
		return;
	switch (scb->dma_type) {
	case M_RD_DMA_TYPE_NONE:
		break;
	case M_RD_PTHRU_WITH_BULK_DATA:
		pci_unmap_single (adapter->dev, scb->dma_h_bulkdata,
				  scb->pthru->dataxferlen,
				  scb->dma_direction);
		break;
	case M_RD_EPTHRU_WITH_BULK_DATA:
		pci_unmap_single (adapter->dev, scb->dma_h_bulkdata,
				  scb->epthru->dataxferlen,
				  scb->dma_direction);
		break;
	case M_RD_PTHRU_WITH_SGLIST:
	{
		int count;
		for (count = 0; count < scb->sglist_count; count++) {
			pci_unmap_single (adapter->dev,
					  scb->dma_h_sglist[count],
					  scb->sgList[count].length,
					  scb->dma_direction);

		}
		break;
	}
	case M_RD_BULK_DATA_ONLY:
		pci_unmap_single (adapter->dev,
				  scb->dma_h_bulkdata,
				  scb->iDataSize, scb->dma_direction);

		break;
	case M_RD_SGLIST_ONLY:
		pci_unmap_sg (adapter->dev,
			      scb->cmd->request_buffer,
			      scb->cmd->use_sg, scb->dma_direction);
		break;
	default:
		break;
	}

	/* Unlink from pending (or issued) queue */
	list_del_init(&scb->list);

	/* Link back into free list */
	scb->state = SCB_FREE;
	scb->cmd = NULL;

	list_add(&scb->list, &adapter->free_list);
}

/*===========================
 * Allocate a SCB structure
 *===========================
 */
static inline scb_t *mega_allocateSCB (adapter_t *adapter, Scsi_Cmnd *cmd)
{
	list_t *head = &adapter->free_list;
	scb_t *scb;

	/* Unlink command from Free List */
	if (!list_empty(head)) {
		scb = list_entry(head->next, scb_t, list);
		list_del_init(head->next);

		scb->state = SCB_ACTIVE;
		scb->cmd = cmd;
		scb->dma_type = M_RD_DMA_TYPE_NONE;

		return scb;
	}

	printk(KERN_WARNING "Megaraid: Could not allocate free SCB!!!\n");

	return NULL;
}

/* Run through the list of completed requests  and finish it */
static void mega_rundoneq (adapter_t *adapter)
{
	Scsi_Cmnd *cmd;
	list_t *pos;

	list_for_each(pos, &adapter->completed_list) {
		cmd = list_entry(pos, Scsi_Cmnd, SCp);

		prefetch(cmd->request.bh);
		cmd->scsi_done(cmd);
	}

	INIT_LIST_HEAD(&adapter->completed_list);
}

/*
 * Runs through the list of pending requests
 * Assumes that mega_lock spin_lock has been acquired.
 */
static void __mega_runpendq (adapter_t *adapter)
{
	scb_t *scb;
	list_t *pos, *next;
	int rc;

	/* Issue any pending commands to the card */
	list_for_each_safe(pos, next, &adapter->pending_list) {
		scb = list_entry(pos, scb_t, list);
		if (scb->state != SCB_ISSUED)
			if ((rc = issue_scb(adapter, scb->raw_mbox, scb)) == -1)
				return;
	}
	return;
}

static inline void mega_runpendq (adapter_t *adapter)
{
	if (!list_empty(&adapter->pending_list))
		__mega_runpendq(adapter);
}

/* Add command to the list of completed requests */

static void mega_cmd_done (adapter_t *adapter, scb_t *scb, int status)
{
	Scsi_Cmnd *cmd;
	mega_passthru *pthru;
	mega_ext_passthru *epthru;
	mbox_t *mbox;
	struct scatterlist *sgList;
	int	islogical;
	u8 c;

	cmd = scb->cmd;
	pthru = scb->pthru;
	epthru = scb->epthru;
	mbox = (mbox_t *) &scb->raw_mbox;

	islogical = adapter->logdrv_chan[cmd->channel];

	/* Special Case to handle PassThrough->XferAddrress > 4GB */
	switch (cmd->cmnd[0]) {
	case INQUIRY:
	case READ_CAPACITY:
		if ( cmd->use_sg ) {
			sgList = (struct scatterlist *)cmd->request_buffer;
			memcpy(sgList[0].address, scb->bounce_buffer, cmd->request_bufflen);
		} else {
			
			memcpy( cmd->request_buffer, scb->bounce_buffer, cmd->request_bufflen);
		}
	}

	mega_freeSCB (adapter, scb);

	/*
	 * Do not return the presence of hard disk on the channel so, inquiry
	 * sent, and returned data==hard disk or removable hard disk and not
	 * logical, request should return failure! - PJ
	 */
	if (cmd->cmnd[0] == INQUIRY && !islogical) {
		if ( cmd->use_sg ) {
			sgList = (struct scatterlist *)cmd->request_buffer;
			memcpy(&c, sgList[0].address, 0x1);
		} else {
			memcpy(&c, cmd->request_buffer, 0x1);
		}
#if 0
		if( (c & 0x1F ) == TYPE_DISK ) {
			status = 0xF0;
		}
#endif
		if(IS_RAID_CH(adapter, cmd->channel) && ((c & 0x1F) == TYPE_DISK)) {
			status = 0xF0;
		}
	}

	/* clear result; otherwise, success returns corrupt value */
	cmd->result = 0;

	if ((cmd->cmnd[0] & M_RD_IOCTL_CMD)) {
		switch (status) {
		case 2:
		case 0xF0:
		case 0xF4:
			cmd->result = (DID_BAD_TARGET << 16) | status;
			break;
		default:
			cmd->result |= status;
		}
	} else {
		/* Convert MegaRAID status to Linux error code */
		switch (status) {
		case 0x00:	/* SUCCESS , i.e. SCSI_STATUS_GOOD */
			cmd->result |= (DID_OK << 16);
			break;

		case 0x02:	/* ERROR_ABORTED, i.e. SCSI_STATUS_CHECK_CONDITION */

			/* set sense_buffer and result fields */
			if( mbox->cmd == MEGA_MBOXCMD_PASSTHRU ||
							mbox->cmd == MEGA_MBOXCMD_PASSTHRU64 ) {
				memcpy(cmd->sense_buffer, pthru->reqsensearea, 14);
				cmd->result = (DRIVER_SENSE << 24) | (DID_OK << 16) |
						(CHECK_CONDITION << 1);
			}
			else if (mbox->cmd == MEGA_MBOXCMD_EXTPASSTHRU) {
				memcpy(cmd->sense_buffer, epthru->reqsensearea, 14);
				cmd->result = (DRIVER_SENSE << 24) | (DID_OK << 16) |
					(CHECK_CONDITION << 1);
			} else {
					cmd->sense_buffer[0] = 0x70;
					cmd->sense_buffer[2] = ABORTED_COMMAND;
					cmd->result |= (CHECK_CONDITION << 1);
			}
			break;

		case 0x08:	/* ERR_DEST_DRIVE_FAILED, i.e. SCSI_STATUS_BUSY */
			cmd->result |= (DID_BUS_BUSY << 16) | status;
			break;

		default:
			cmd->result |= (DID_BAD_TARGET << 16) | status;
		}
	}

	/* Add Scsi_Command to end of completed queue */
	list_add_tail(SCSI_LIST(cmd), &adapter->completed_list);
}

/*-------------------------------------------------------------------
 *
 *                 Build a SCB from a Scsi_Cmnd
 *
 * Returns a SCB pointer, or NULL
 * If NULL is returned, the scsi_done function MUST have been called
 *
 *-------------------------------------------------------------------*/

static scb_t *mega_build_cmd (adapter_t *adapter, Scsi_Cmnd *cmd)
{
	scb_t *scb;
	mbox_t *mbox;
	mega_passthru *pthru;
	mega_ext_passthru *epthru;
	long seg;
	char islogical;
	int		max_ldrv_num;
	int		channel = 0;
	int		target = 0;
	int		ldrv_num = 0;	/* logical drive number */

	if ((cmd->cmnd[0] == MEGADEVIOC))
		return megadev_doioctl (adapter, cmd);

	if ((cmd->cmnd[0] == M_RD_IOCTL_CMD)
		    || (cmd->cmnd[0] == M_RD_IOCTL_CMD_NEW))
	{
		printk(KERN_WARNING "megaraid ioctl: older interface - "
				"not supported.\n");
		return NULL;
	}

#if 0
	islogical = ( (cmd->channel >= adapter->productInfo.SCSIChanPresent) &&
					(cmd->channel <= adapter->host->max_channel) );
#endif
	/*
	 * We know on what channels our logical drives are - mega_findCard()
	 */
	islogical = adapter->logdrv_chan[cmd->channel];


	/*
	 * The theory: If physical drive is chosen for boot, all the physical
	 * devices are exported before the logical drives, otherwise physical
	 * devices are pushed after logical drives, in which case - Kernel sees
	 * the physical devices on virtual channel which is obviously converted
	 * to actual channel on the HBA.
	 */
	if( adapter->boot_pdrv_enabled ) {
		if( islogical ) {
			/* logical channel */
			channel = cmd->channel - adapter->productInfo.SCSIChanPresent;
		}
		else {
			channel = cmd->channel; /* this is physical channel */
			target = cmd->target;

			/*
			 * boot from a physical disk, that disk needs to be exposed first
			 * IF both the channels are SCSI, then booting from the second
			 * channel is not allowed.
			 */
			if( target == 0 ) {
				target = adapter->boot_pdrv_tgt;
			}
			else if( target == adapter->boot_pdrv_tgt ) {
				target = 0;
			}
		}
	}
	else {
		if( islogical ) {
			channel = cmd->channel; /* this is the logical channel */
		}
		else {
			channel = cmd->channel - NVIRT_CHAN;	/* physical channel */
			target = cmd->target;
		}
	}

	if (!adapter->support_ext_cdb) {
		if (!islogical && cmd->lun != 0) {
			cmd->result = (DID_BAD_TARGET << 16);
			cmd->scsi_done(cmd);
			return NULL;
		}
	}

	if (!islogical && cmd->target == skip_id) {
		cmd->result = (DID_BAD_TARGET << 16);
		cmd->scsi_done(cmd);
		return NULL;
	}

	if (islogical) {

		/* have just LUN 0 for each target on virtual channels */
		if (cmd->lun) {
			cmd->result = (DID_BAD_TARGET << 16);
			cmd->scsi_done(cmd);
			return NULL;
		}

		ldrv_num = mega_get_ldrv_num(adapter, cmd, channel);

	    max_ldrv_num = (adapter->flag & BOARD_40LD) ?
						FC_MAX_LOGICAL_DRIVES : MAX_LOGICAL_DRIVES;

		 /*
		  * max_lun increases by 0x80 if some logical drive was deleted.
		  */
		if (adapter->read_ldidmap)
			max_ldrv_num += 0x80;

		if (max_ldrv_num > max_ldrv_num ) {
			cmd->result = (DID_BAD_TARGET << 16);
			cmd->scsi_done(cmd);
			return NULL;
		}

	} else {
		if ( cmd->lun > 7) {
				/* Do not support lun >7 for physically accessed devices */
			cmd->result = (DID_BAD_TARGET << 16);
			cmd->scsi_done(cmd);
			return NULL;
		}
	}
	/*-----------------------------------------------------
	 *
	 *               Logical drive commands
	 *
	 *-----------------------------------------------------*/
	if (islogical) {
		switch (cmd->cmnd[0]) {
		case TEST_UNIT_READY:
			memset(cmd->request_buffer, 0, cmd->request_bufflen);
			cmd->result = (DID_OK << 16);
			cmd->scsi_done(cmd);
			return NULL;

		case MODE_SENSE:
			memset(cmd->request_buffer, 0, cmd->cmnd[4]);
			cmd->result = (DID_OK << 16);
			cmd->scsi_done(cmd);
			return NULL;

		case READ_CAPACITY:
		case INQUIRY:
			if(!(adapter->flag & (1L << cmd->channel))) {
				printk(KERN_NOTICE
					"scsi%d: scanning virtual channel %d for logical drives.\n",
					adapter->host->host_no, channel);

				adapter->flag |= (1L << cmd->channel);
			}

			/* Allocate a SCB and initialize passthru */
			if (!(scb = mega_allocateSCB (adapter, cmd))) {
				cmd->result = (DID_ERROR << 16);
				cmd->scsi_done(cmd);
				return NULL;
			}
			pthru = scb->pthru;

			mbox = (mbox_t *) & scb->raw_mbox;
			memset(mbox, 0, sizeof (scb->raw_mbox));
			memset(pthru, 0, sizeof (mega_passthru));
			pthru->timeout = 0;
			pthru->ars = 1;
			pthru->reqsenselen = 14;
			pthru->islogical = 1;
			pthru->logdrv = ldrv_num;
			pthru->cdblen = cmd->cmd_len;

			/*Not sure about the direction */
			scb->dma_direction = PCI_DMA_BIDIRECTIONAL;
			scb->dma_type = M_RD_PTHRU_WITH_BULK_DATA;

/* Special Code to use bounce buffer for READ_CAPA/INQ */
			memset(scb->bounce_buffer, 0, cmd->request_bufflen);
			pthru->dataxferaddr = scb->dma_bounce_buffer;
			scb->dma_type = M_RD_DMA_TYPE_NONE;

			pthru->dataxferlen = cmd->request_bufflen;
			memcpy(pthru->cdb, cmd->cmnd, cmd->cmd_len);

			/* Initialize mailbox area */
			mbox->cmd = MEGA_MBOXCMD_PASSTHRU;

			mbox->xferaddr = scb->dma_passthruhandle64;
			return scb;

		case READ_6:
		case WRITE_6:
		case READ_10:
		case WRITE_10:
			/* Allocate a SCB and initialize mailbox */
			if (!(scb = mega_allocateSCB (adapter, cmd))) {
				cmd->result = (DID_ERROR << 16);
				cmd->scsi_done(cmd);
				return NULL;
			}
			mbox = (mbox_t *) & scb->raw_mbox;

			memset(mbox, 0, sizeof (scb->raw_mbox));
			mbox->logdrv = ldrv_num;

#if USE_64BIT_COMMANDS
			if (adapter->flag & BOARD_64BIT)
				mbox->cmd = (*cmd->cmnd == READ_6 || *cmd->cmnd == READ_10) ?
								MEGA_MBOXCMD_LREAD64 : MEGA_MBOXCMD_LWRITE64;
			else
#endif
				mbox->cmd = (*cmd->cmnd == READ_6 || *cmd->cmnd == READ_10) ?
								MEGA_MBOXCMD_LREAD : MEGA_MBOXCMD_LWRITE;

			/* 6-byte */
			if (*cmd->cmnd == READ_6 || *cmd->cmnd == WRITE_6) {
				mbox->numsectors = (u32) cmd->cmnd[4];
				mbox->lba =
				    ((u32) cmd->cmnd[1] << 16) |
				    ((u32) cmd->cmnd[2] << 8) |
				    (u32) cmd->cmnd[3];
				mbox->lba &= 0x1FFFFF;

#if STATS
				if (*cmd->cmnd == READ_6) {
					adapter->nReads[(int) ldrv_num%0x80]++;
					adapter->nReadBlocks[(int) ldrv_num%0x80] +=
						mbox->numsectors;
				} else {
					adapter->nWrites[(int) ldrv_num%0x80]++;
					adapter->nWriteBlocks[(int) ldrv_num%0x80] +=
						mbox->numsectors;
				}
#endif
			}

			/* 10-byte */
			if (*cmd->cmnd == READ_10 || *cmd->cmnd == WRITE_10) {
				mbox->numsectors =
				    (u32) cmd->cmnd[8] |
				    ((u32) cmd->cmnd[7] << 8);
				mbox->lba =
				    ((u32) cmd->cmnd[2] << 24) |
				    ((u32) cmd->cmnd[3] << 16) |
				    ((u32) cmd->cmnd[4] << 8) |
				    (u32) cmd->cmnd[5];

#if STATS
				if (*cmd->cmnd == READ_10) {
					adapter->nReads[(int) ldrv_num%0x80]++;
					adapter->nReadBlocks[(int) ldrv_num%0x80] +=
						mbox->numsectors;
				} else {
					adapter->nWrites[(int) ldrv_num%0x80]++;
					adapter->nWriteBlocks[(int) ldrv_num%0x80] +=
						mbox->numsectors;
				}
#endif
			}

			/* 12-byte */
			if (*cmd->cmnd == READ_12 || *cmd->cmnd == WRITE_12) {
				mbox->lba =
				    ((u32) cmd->cmnd[2] << 24) |
				    ((u32) cmd->cmnd[3] << 16) |
				    ((u32) cmd->cmnd[4] << 8) |
				    (u32) cmd->cmnd[5];

				mbox->numsectors =
				    ((u32) cmd->cmnd[6] << 24) |
				    ((u32) cmd->cmnd[7] << 16) |
				    ((u32) cmd->cmnd[8] << 8) |
				    (u32) cmd->cmnd[9];

#if STATS
				if (*cmd->cmnd == READ_12) {
					adapter->nReads[(int) ldrv_num%0x80]++;
					adapter->nReadBlocks[(int) ldrv_num%0x80] +=
						mbox->numsectors;
				} else {
					adapter->nWrites[(int) ldrv_num%0x80]++;
					adapter->nWriteBlocks[(int) ldrv_num%0x80] +=
						mbox->numsectors;
				}
#endif
			}

			if (*cmd->cmnd == READ_6 || *cmd->cmnd == READ_10
					|| *cmd->cmnd == READ_12)
				scb->dma_direction = PCI_DMA_FROMDEVICE;
			else	/*WRITE_6 or WRITE_10 */
				scb->dma_direction = PCI_DMA_TODEVICE;

			/* Calculate Scatter-Gather info */
			mbox->numsgelements = mega_build_sglist (adapter, scb,
								 (u32 *)&mbox->xferaddr, (u32 *)&seg);

			scb->iDataSize = seg;

			if (mbox->numsgelements)
				scb->dma_type = M_RD_SGLIST_ONLY;
			else
				scb->dma_type = M_RD_BULK_DATA_ONLY;

			return scb;
		default:
			cmd->result = (DID_BAD_TARGET << 16);
			cmd->scsi_done(cmd);
			return NULL;
		}
	}
	/*-----------------------------------------------------
	 *
	 *               Passthru drive commands
	 *
	 *-----------------------------------------------------*/
	else {
		/* Allocate a SCB and initialize passthru */
		if (!(scb = mega_allocateSCB (adapter, cmd))) {
			cmd->result = (DID_ERROR << 16);
			cmd->scsi_done(cmd);
			return NULL;
		}

		mbox = (mbox_t *) scb->raw_mbox;
		memset(mbox, 0, sizeof (scb->raw_mbox));

		if ( adapter->support_ext_cdb && cmd->cmd_len > 10 ) {
			epthru = mega_prepare_extpassthru(adapter, scb, cmd, channel,
					target);
			mbox->cmd = MEGA_MBOXCMD_EXTPASSTHRU;
			mbox->xferaddr = scb->dma_ext_passthruhandle64;

			if (epthru->numsgelements)
				scb->dma_type = M_RD_PTHRU_WITH_SGLIST;
			else
				scb->dma_type = M_RD_EPTHRU_WITH_BULK_DATA;
		} else {
			pthru = mega_prepare_passthru(adapter, scb, cmd, channel, target);

			/* Initialize mailbox */
			mbox->cmd = MEGA_MBOXCMD_PASSTHRU;
			mbox->xferaddr = scb->dma_passthruhandle64;

			if (pthru->numsgelements)
				scb->dma_type = M_RD_PTHRU_WITH_SGLIST;
			else
				scb->dma_type = M_RD_PTHRU_WITH_BULK_DATA;
		}
		return scb;
	}
	return NULL;
}

static inline int
mega_get_ldrv_num(adapter_t *adapter, Scsi_Cmnd *cmd, int channel)
{
	int		tgt;
	int		ldrv_num;

	tgt = cmd->target;
	
	if ( tgt > 7 )
		tgt--;	/* we do not get inquires for tgt 7 */

	ldrv_num = (channel * 15) + tgt;

	/*
	 * If we have a logical drive with boot enabled, project it first
	 */
	if( adapter->boot_ldrv_enabled ) {
		if( ldrv_num == 0 ) {
			ldrv_num = adapter->boot_ldrv;
		}
		else {
			if( ldrv_num <= adapter->boot_ldrv ) {
				ldrv_num--;
			}
		}
	}

	/*
	 * If "delete logical drive" feature is enabled on this controller.
	 * Do only if at least one delete logical drive operation was done.
	 *
	 * Also, after logical drive deletion, instead of logical drive number,
	 * the value returned should be 0x80+logical drive id.
	 *
	 * These is valid only for IO commands.
	 */

	if (adapter->support_random_del && adapter->read_ldidmap )
		switch (cmd->cmnd[0]) {
		case READ_6:	/* fall through */
		case WRITE_6:	/* fall through */
		case READ_10:	/* fall through */
		case WRITE_10:
			ldrv_num += 0x80;
		}

	return ldrv_num;
}


static mega_passthru *
mega_prepare_passthru(adapter_t *adapter, scb_t *scb, Scsi_Cmnd *cmd, int channel, int target)
{
	mega_passthru *pthru;

	pthru = scb->pthru;
	memset(pthru, 0, sizeof (mega_passthru));

	/* set adapter timeout value to 10 min. for tape drive	*/
	/* 0=6sec/1=60sec/2=10min/3=3hrs 			*/
	pthru->timeout = 2;
	pthru->ars = 1;
	pthru->reqsenselen = 14;
	pthru->islogical = 0;
	pthru->channel = (adapter->flag & BOARD_40LD) ? 0 : channel;
	pthru->target = (adapter->flag & BOARD_40LD) ?
	    (channel << 4) | target : target;
	pthru->cdblen = cmd->cmd_len;
	pthru->logdrv = cmd->lun;

	memcpy(pthru->cdb, cmd->cmnd, cmd->cmd_len);

	/* Not sure about the direction */
	scb->dma_direction = PCI_DMA_BIDIRECTIONAL;

	/* Special Code for Handling READ_CAPA/ INQ using bounce buffers */
	switch (cmd->cmnd[0]) {
	case INQUIRY:
	case READ_CAPACITY:
		if(!(adapter->flag & (1L << cmd->channel))) {
			printk(KERN_NOTICE
				"scsi%d: scanning physical channel %d for devices.\n",
				adapter->host->host_no, channel);

			adapter->flag |= (1L << cmd->channel);
		}


		pthru->numsgelements = 0;
		memset(scb->bounce_buffer, 0, cmd->request_bufflen);
		pthru->dataxferaddr = scb->dma_bounce_buffer;
		pthru->dataxferlen = cmd->request_bufflen;
		break;
	default:
		pthru->numsgelements = mega_build_sglist(adapter, scb,
				&pthru->dataxferaddr, &pthru->dataxferlen);
		break;
	}
	return pthru;
}

static mega_ext_passthru *
mega_prepare_extpassthru(adapter_t *adapter, scb_t *scb, Scsi_Cmnd *cmd, int channel, int target)
{
	mega_ext_passthru *epthru;

	epthru = scb->epthru;
	memset(epthru, 0, sizeof(mega_ext_passthru));

	/* set adapter timeout value to 10 min. for tape drive	*/
	/* 0=6sec/1=60sec/2=10min/3=3hrs 			*/
	epthru->timeout = 2;
	epthru->ars = 1;
	epthru->reqsenselen = 14;
	epthru->islogical = 0;
	epthru->channel = (adapter->flag & BOARD_40LD) ? 0 : channel;
	epthru->target = (adapter->flag & BOARD_40LD) ?
	    (channel << 4) | target : target;
	epthru->cdblen = cmd->cmd_len;
	epthru->logdrv = cmd->lun;

	memcpy(epthru->cdb, cmd->cmnd, cmd->cmd_len);

	/* Not sure about the direction */
	scb->dma_direction = PCI_DMA_BIDIRECTIONAL;

	/* Special Code for Handling READ_CAPA/ INQ using bounce buffers */
	switch (cmd->cmnd[0]) {
	case INQUIRY:
	case READ_CAPACITY:
		if(!(adapter->flag & (1L << cmd->channel))) {
			printk(KERN_NOTICE
				"scsi%d: scanning physical channel %d for devices.\n",
				adapter->host->host_no, channel);

			adapter->flag |= (1L << cmd->channel);
		}


		epthru->numsgelements = 0;
		memset(scb->bounce_buffer, 0, cmd->request_bufflen);
		epthru->dataxferaddr = scb->dma_bounce_buffer;
		epthru->dataxferlen = cmd->request_bufflen;
		break;
	default:
		epthru->numsgelements = mega_build_sglist(adapter, scb,
				&epthru->dataxferaddr, &epthru->dataxferlen);
		break;
	}
	return epthru;
}

/* Handle Driver Level IOCTLs
 * Return value of 0 indicates this function could not handle , so continue
 * processing
*/

static inline int mega_driver_ioctl (adapter_t *adapter, Scsi_Cmnd *cmd)
{
	unsigned char *data = (unsigned char *) cmd->request_buffer;
	mega_driver_info driver_info;

	/* If this is not our command dont do anything */
	if (cmd->cmnd[0] != M_RD_DRIVER_IOCTL_INTERFACE)
		return 0;

	switch (cmd->cmnd[1]) {
	case GET_DRIVER_INFO:
		if (cmd->request_bufflen < sizeof (driver_info)) {
			cmd->result = DID_BAD_TARGET << 16;
			cmd->scsi_done(cmd);
			return 1;
		}

		driver_info.size = sizeof (driver_info) - sizeof (int);
		driver_info.version = MEGARAID_IOCTL_VERSION;
		memcpy(data, &driver_info, sizeof (driver_info));
		break;
	default:
		cmd->result = DID_BAD_TARGET << 16;
	}

	cmd->scsi_done(cmd);
	return 1;
}

static void inline set_mbox_xfer_addr (adapter_t *adapter, scb_t *scb,
		    mega_ioctl_mbox * mbox, u32 direction)
{

	switch (direction) {
	case TO_DEVICE:		scb->dma_direction = PCI_DMA_TODEVICE;		break;
	case FROM_DEVICE:	scb->dma_direction = PCI_DMA_FROMDEVICE;	break;
	case FROMTO_DEVICE: scb->dma_direction = PCI_DMA_BIDIRECTIONAL;	break;
	}

	scb->dma_h_bulkdata = pci_map_single (adapter->dev, scb->buff_ptr,
			      scb->iDataSize, scb->dma_direction);
	mbox->xferaddr = scb->dma_h_bulkdata;
	scb->dma_type = M_RD_BULK_DATA_ONLY;
}

/*--------------------------------------------------------------------
 * Interrupt service routine
 *--------------------------------------------------------------------*/
static void megaraid_isr(int irq, void *devp, struct pt_regs *regs)
{
	u8 completed[MAX_FIRMWARE_STATUS];
	adapter_t *adapter = devp;
	u_char qCnt, qStatus;
	unsigned long flags;
	u_char byte, idx;
	u32 dword = 0;
	scb_t *scb;

	spin_lock_irqsave(&adapter->lock, flags);

	/* Check if a valid interrupt is pending */
	if (!(adapter->flag & BOARD_QUARTZ))
		goto non_quartz_irq_check;

	dword = RDOUTDOOR(adapter);
	if (dword != 0x10001234) {
		spin_unlock_irqrestore(&adapter->lock, flags);
		/* Spurious interrupt */
		return;
	}
	WROUTDOOR (adapter, 0x10001234);

non_quartz_irq_check_back:


	qCnt = 0xff;
	while((qCnt = (volatile u8)adapter->mbox->numstatus) == 0xFF)  cpu_relax();
	(volatile u8)adapter->mbox->numstatus = 0xFF;

	/*
	 * decrement the pending queue counter
	 */
	atomic_sub(qCnt, &adapter->pend_cmds);

	qStatus = adapter->mbox->status;

	memcpy(completed, (void *)((volatile u8 *)adapter->mbox->completed), qCnt);

	if (!(adapter->flag & BOARD_QUARTZ))
		goto non_quartz_irq_ack;

	/* Acknowledge interrupt */
	/* In this case mbox contains physical address */
	WRINDOOR (adapter, 0x2);
	while( RDINDOOR(adapter) & 0x02 ) cpu_relax();

non_quartz_irq_ack_back:

	for (idx = 0; idx < qCnt; idx++) {
		scb = adapter->scbList[completed[idx] - 1];
		/*
		 * Assuming that the scsi command, for which 
		 * an abort request was received earlier, has 
		 * completed.
		 */
		if (scb->state == SCB_RESET) {
			mega_freeSCB (adapter, scb);
			scb->cmd->result = (DID_RESET << 16);
			list_add_tail(SCSI_LIST(scb->cmd), &adapter->completed_list);
			continue;
		}

		/* We don't want the ISR routine to touch M_RD_IOCTL_CMD_NEW
		 * commands, so don't mark them as complete, instead we pop
		 * their semaphore so that the queue routine can finish them off
		 */
		if (scb->cmd->cmnd[0] != M_RD_IOCTL_CMD_NEW)
			/* Mark command as completed */
			mega_cmd_done(adapter, scb, qStatus);
		else {
			/* save the status byte for the queue routine to use */
			scb->cmd->result = qStatus;
			up(&scb->ioctl_sem);
		}
	}
	mega_rundoneq(adapter);

	/* Loop through any pending requests */
	if (!adapter->quiescent) {
		mega_runpendq(adapter);
	}

	spin_unlock_irqrestore(&adapter->lock, flags);
	return;

/* rare codepath moved offline: */

non_quartz_irq_check:
	byte = irq_state(adapter);
	if (!(byte & VALID_INTR_BYTE)) {
		spin_unlock_irqrestore(&adapter->lock, flags);
		/* Spurious interrupt */
		return;
	}

	set_irq_state(adapter, byte);
	goto non_quartz_irq_check_back;

non_quartz_irq_ack:
	irq_ack(adapter);
	goto non_quartz_irq_ack_back;
}

/*==================================================*/
/* Wait until the controller's mailbox is available */
/*==================================================*/

static inline int mega_busyWaitMbox (adapter_t *adapter)
{
	if (adapter->mbox->busy)
		return __mega_busyWaitMbox(adapter);
	return 0;
}

static int __mega_busyWaitMbox (adapter_t *adapter)
{
	volatile mbox_t *mbox = adapter->mbox;
	long counter;

	for (counter = 0; counter < 1000000; counter++) {
		if (!mbox->busy)
			return 0;
		udelay(1);
	}
	return -1;		/* give up after 1 second */
}

/*=====================================================
 * Post a command to the card
 *
 * Arguments:
 *   adapter_t *adapter - Controller structure
 *   u_char *raw_mbox - Mailbox area, 16 bytes
 *   scb_t *scb   - SCB posting (or NULL if N/A)
 * Return Value: (added on 7/26 for 40ld/64bit)
 *   -1: the command was not actually issued out
 *   other cases:
 *     return ScsiStatus, i.e. mbox->status
 *=====================================================
 */
static inline int issue_scb(adapter_t *adapter, u_char *raw_mbox, scb_t *scb)
{
#if USE_64BIT_COMMANDS
	volatile mbox64_t *mbox64 = adapter->mbox64;
#endif
	volatile mbox_t *mbox = adapter->mbox;
#if BITS_PER_LONG == 64
	u64 phys_mbox;
#else
	u32 phys_mbox;
#endif

	if (adapter->mbox->busy)
		return -1;

	raw_mbox[0x1] = scb->idx + 1;	/* Set cmdid */
	raw_mbox[0xF] = 1;				/* Set busy */

	/* In this case mbox contains physical address */
	phys_mbox = adapter->adjdmahandle64;

	/* Copy mailbox data into host structure */
	memcpy((char *) mbox, raw_mbox, 16);

#if USE_64BIT_COMMANDS
	switch (raw_mbox[0]) {
	case MEGA_MBOXCMD_LREAD64:
	case MEGA_MBOXCMD_LWRITE64:
		mbox64->xfer_segment_lo = mbox->xferaddr;
		mbox64->xfer_segment_hi = 0;
		mbox->xferaddr = 0xFFFFFFFF;
		break;
	default:
		mbox64->xfer_segment_lo = 0;
		mbox64->xfer_segment_hi = 0;
	}
#endif

	atomic_inc(&adapter->pend_cmds);

	scb->state = SCB_ISSUED;

	/* Kick IO, issue non-blocking command: */
	if (adapter->flag & BOARD_QUARTZ) {
		mbox->poll = 0;
		mbox->ack = 0;
		WRINDOOR (adapter, phys_mbox | 0x1);
	} else {
		irq_enable(adapter);
		issue_command(adapter);
	}
	return 0;

}

static int issue_scb_block(adapter_t *adapter, u_char *raw_mbox)
{
#if USE_64BIT_COMMANDS
	volatile mbox64_t *mbox64 = adapter->mbox64;
#endif
	volatile mbox_t *mbox = adapter->mbox;
	u_char byte;
#if BITS_PER_LONG == 64
	u64 phys_mbox;
#else
	u32 phys_mbox;
#endif

	raw_mbox[0x1] = 0xFE;	/* Set cmdid */
	raw_mbox[0xF] = 1;	/* Set busy */

	/* In this case mbox contains physical address */
	phys_mbox = adapter->adjdmahandle64;

	/* Wait until mailbox is free */
	if (mega_busyWaitMbox (adapter))
		goto bug_blocked_mailbox;

	/* Copy mailbox data into host structure */
	memcpy((char *) mbox, raw_mbox, 16);

#if USE_64BIT_COMMANDS
	switch (raw_mbox[0]) {
	case MEGA_MBOXCMD_LREAD64:
	case MEGA_MBOXCMD_LWRITE64:
		mbox64->xfer_segment_lo = mbox->xferaddr;
		mbox64->xfer_segment_hi = 0;
		mbox->xferaddr = 0xFFFFFFFF;
		break;
	default:
		mbox64->xfer_segment_lo = 0;
		mbox64->xfer_segment_hi = 0;
	}
#endif

	/* Kick IO, issue blocking command */
	if (adapter->flag & BOARD_QUARTZ) {
		mbox->poll = 0;
		mbox->ack = 0;
		mbox->numstatus = 0xFF;
		mbox->status = 0xFF;
		WRINDOOR (adapter, phys_mbox | 0x1);

		while (mbox->numstatus == 0xFF)
			cpu_relax();
		while (mbox->status == 0xFF)
			cpu_relax();
		while (mbox->poll != 0x77)
			cpu_relax();
		mbox->poll = 0;
		mbox->ack = 0x77;

		WRINDOOR(adapter, phys_mbox | 0x2);
		while (RDINDOOR(adapter) & 0x2)
			cpu_relax();
	} else {
		irq_disable(adapter);
		issue_command(adapter);

		while (!((byte = irq_state(adapter)) & INTR_VALID))
			cpu_relax();
		set_irq_state(adapter, byte);
		irq_enable(adapter);
		irq_ack(adapter);
	}
	return mbox->status;

bug_blocked_mailbox:
	printk("Blocked mailbox......!!\n");
	udelay (1000);
	return -1;
}

/*-------------------------------------------------------------------
 * Copies data to SGLIST
 *-------------------------------------------------------------------*/
/* Note:
	For 64 bit cards, we need a minimum of one SG element for read/write
*/

static int mega_build_sglist(adapter_t *adapter, scb_t *scb, u32 *buf, u32 *len)
{
#if USE_64BIT_COMMANDS
	mbox_t *mbox = (mbox_t *) scb->raw_mbox;
#endif
	struct scatterlist *sgList;
	int sgcnt;
	int idx;

	/* Scatter-gather not used */
	if (!scb->cmd->use_sg) {
		scb->dma_h_bulkdata = pci_map_single (adapter->dev,
				      scb->cmd->request_buffer,
				      scb->cmd->request_bufflen,
				      scb->dma_direction);
		/* We need to handle special commands like READ64, WRITE64
		   as they need a minimum of 1 SG irrespective of actually SG
		 */
#if USE_64BIT_COMMANDS
		if ((adapter->flag & BOARD_64BIT) &&
			    ((mbox->cmd == MEGA_MBOXCMD_LREAD64) ||
			     (mbox->cmd == MEGA_MBOXCMD_LWRITE64))) {
			scb->sg64List[0].address = scb->dma_h_bulkdata;
			scb->sg64List[0].length = scb->cmd->request_bufflen;
			*buf = scb->dma_sghandle64;
			*len = 0;
			scb->sglist_count = 1;
			return 1;
		} else
#endif
		{
			*buf = scb->dma_h_bulkdata;
			*len = (u32) scb->cmd->request_bufflen;
		}
		return 0;
	}

	sgList = (struct scatterlist *) scb->cmd->request_buffer;
	/* Copy Scatter-Gather list info into controller structure */
	sgcnt = pci_map_sg(adapter->dev,
							sgList, scb->cmd->use_sg, scb->dma_direction);

	for (idx = 0; idx < sgcnt; idx++, sgList++) {

#if USE_64BIT_COMMANDS
		if ((adapter->flag & BOARD_64BIT) &&
		    ((mbox->cmd == MEGA_MBOXCMD_LREAD64) ||
		     (mbox->cmd == MEGA_MBOXCMD_LWRITE64))) {
			scb->sg64List[idx].address = sg_dma_address(sgList);
			scb->sg64List[idx].length = sg_dma_len(sgList);
		} else
#endif
		{
			scb->sgList[idx].address = sg_dma_address(sgList);
			scb->sgList[idx].length = sg_dma_len(sgList);
		}
	}
	/* Reset pointer and length fields */
	*buf = scb->dma_sghandle64;
	scb->sglist_count = scb->cmd->use_sg;
	*len = (u32) scb->cmd->request_bufflen;

	/* Return count of SG requests */
	return sgcnt;
}

/*--------------------------------------------------------------------
 * Initializes the address of the controller's mailbox register
 *  The mailbox register is used to issue commands to the card.
 *  Format of the mailbox area:
 *   00 01 command
 *   01 01 command id
 *   02 02 # of sectors
 *   04 04 logical bus address
 *   08 04 physical buffer address
 *   0C 01 logical drive #
 *   0D 01 length of scatter/gather list
 *   0E 01 reserved
 *   0F 01 mailbox busy
 *   10 01 numstatus byte
 *   11 01 status byte
 *--------------------------------------------------------------------*/
static int mega_register_mailbox (adapter_t *adapter, u32 paddr)
{
	/* align on 16-byte boundary */
	adapter->mbox = &adapter->mailbox64ptr->mbox;

#if BITS_PER_LONG == 64
	adapter->mbox = (mbox_t *) ((((u64) adapter->mbox) + 16) & ((u64) (-1) ^ 0x0F));
	adapter->adjdmahandle64 = (adapter->dma_handle64 + 16) & ((u64) (-1) ^ 0x0F);
	adapter->mbox64 = (mbox64_t *) ((u_char *) adapter->mbox - sizeof (u64));
	paddr = (paddr + 4 + 16) & ((u64) (-1) ^ 0x0F);
#else
	adapter->mbox = (mbox_t *)((((u32)adapter->mbox) + 16) & 0xFFFFFFF0);
	adapter->adjdmahandle64 = ((adapter->dma_handle64 + 16) & 0xFFFFFFF0);
	adapter->mbox64 = (mbox64_t *) ((u_char *) adapter->mbox - 8);
	paddr = (paddr + 4 + 16) & 0xFFFFFFF0;
#endif

	/* Register mailbox area with the firmware */
	if (!(adapter->flag & BOARD_QUARTZ)) {
		outb_p(paddr & 0xFF,		 adapter->host->io_port + MBOX_PORT0);
		outb_p((paddr >> 8) & 0xFF,  adapter->host->io_port + MBOX_PORT1);
		outb_p((paddr >> 16) & 0xFF, adapter->host->io_port + MBOX_PORT2);
		outb_p((paddr >> 24) & 0xFF, adapter->host->io_port + MBOX_PORT3);
		outb_p(ENABLE_MBOX_BYTE, adapter->host->io_port + ENABLE_MBOX_REGION);
		irq_ack(adapter);
		irq_enable(adapter);
	}
	return 0;
}

/*---------------------------------------------------------------------------
 * mega_Convert8ldTo40ld() -- takes all info in AdapterInquiry structure and
 * puts it into ProductInfo and Enquiry3 structures for later use
 *---------------------------------------------------------------------------*/
static void mega_Convert8ldTo40ld (mega_RAIDINQ * inquiry,
		       mega_Enquiry3 * enquiry3,
		       megaRaidProductInfo * productInfo)
{
	int i;

	productInfo->MaxConcCmds = inquiry->AdpInfo.MaxConcCmds;
	enquiry3->rbldRate = inquiry->AdpInfo.RbldRate;
	productInfo->SCSIChanPresent = inquiry->AdpInfo.ChanPresent;

	for (i = 0; i < 4; i++) {
		productInfo->FwVer[i] = inquiry->AdpInfo.FwVer[i];
		productInfo->BiosVer[i] = inquiry->AdpInfo.BiosVer[i];
	}
	enquiry3->cacheFlushInterval = inquiry->AdpInfo.CacheFlushInterval;
	productInfo->DramSize = inquiry->AdpInfo.DramSize;

	enquiry3->numLDrv = inquiry->LogdrvInfo.NumLDrv;

	for (i = 0; i < MAX_LOGICAL_DRIVES; i++) {
		enquiry3->lDrvSize[i] = inquiry->LogdrvInfo.LDrvSize[i];
		enquiry3->lDrvProp[i] = inquiry->LogdrvInfo.LDrvProp[i];
		enquiry3->lDrvState[i] = inquiry->LogdrvInfo.LDrvState[i];
	}

	for (i = 0; i < (MAX_PHYSICAL_DRIVES); i++)
		enquiry3->pDrvState[i] = inquiry->PhysdrvInfo.PDrvState[i];
}

/*-------------------------------------------------------------------
 * Issue an adapter info query to the controller
 *-------------------------------------------------------------------*/
static int mega_i_query_adapter (adapter_t *adapter)
{
	dma_addr_t raid_inq_dma_handle, prod_info_dma_handle, enquiry3_dma_handle;
	mega_Enquiry3 *enquiry3Pnt;
	u_char raw_mbox[16];
	mbox_t *mbox;
	int retval;

	/* Initialize adapter inquiry mailbox */

	mbox = (mbox_t *) raw_mbox;

	memset((void *) adapter->mega_buffer, 0, sizeof (adapter->mega_buffer));
	memset(mbox, 0, 16);

/*
 * Try to issue Enquiry3 command
 * if not succeeded, then issue MEGA_MBOXCMD_ADAPTERINQ command and
 * update enquiry3 structure
 */
	enquiry3_dma_handle = pci_map_single (adapter->dev,
			      (void *) adapter->mega_buffer,
			      (2 * 1024L), PCI_DMA_FROMDEVICE);

	mbox->xferaddr = enquiry3_dma_handle;

	/* Initialize mailbox databuffer addr */
	enquiry3Pnt = (mega_Enquiry3 *) adapter->mega_buffer;
	/* point mega_Enguiry3 to the data buf */

	raw_mbox[0] = FC_NEW_CONFIG;	/* i.e. mbox->cmd=0xA1 */
	raw_mbox[2] = NC_SUBOP_ENQUIRY3;	/* i.e. 0x0F */
	raw_mbox[3] = ENQ3_GET_SOLICITED_FULL;	/* i.e. 0x02 */

	/* Issue a blocking command to the card */
	if ((retval = issue_scb_block(adapter, raw_mbox))) {
		/* the adapter does not support 40ld */
		mega_RAIDINQ adapterInquiryData;
		mega_RAIDINQ *adapterInquiryPnt = &adapterInquiryData;

		raid_inq_dma_handle = pci_map_single (adapter->dev,
				      (void *) adapterInquiryPnt,
				      sizeof (mega_RAIDINQ),
				      PCI_DMA_FROMDEVICE);
		mbox->xferaddr = raid_inq_dma_handle;
		mbox->cmd = MEGA_MBOXCMD_ADAPTERINQ;	/*issue old 0x05 command to adapter */
		/* Issue a blocking command to the card */ ;
		retval = issue_scb_block(adapter, raw_mbox);

		pci_unmap_single (adapter->dev,
				  raid_inq_dma_handle,
				  sizeof (mega_RAIDINQ), PCI_DMA_FROMDEVICE);

		/*update Enquiry3 and ProductInfo structures with mega_RAIDINQ structure*/
		mega_Convert8ldTo40ld (adapterInquiryPnt,
				       enquiry3Pnt,
				       (megaRaidProductInfo *) & adapter->
				       productInfo);

	} else {		/*adapter supports 40ld */
		adapter->flag |= BOARD_40LD;

		pci_unmap_single (adapter->dev,
				  enquiry3_dma_handle,
				  (2 * 1024L), PCI_DMA_FROMDEVICE);
/*get productInfo, which is static information and will be unchanged*/
		prod_info_dma_handle
		    = pci_map_single (adapter->dev,
				      (void *) &adapter->productInfo,
				      sizeof (megaRaidProductInfo),
				      PCI_DMA_FROMDEVICE);
		mbox->xferaddr = prod_info_dma_handle;

		raw_mbox[0] = FC_NEW_CONFIG;	/* i.e. mbox->cmd=0xA1 */
		raw_mbox[2] = NC_SUBOP_PRODUCT_INFO;	/* i.e. 0x0E */

		if ((retval = issue_scb_block(adapter, raw_mbox)))
			printk("megaraid: Product_info cmd failed with error: %d\n",
				retval);

		pci_unmap_single (adapter->dev,
				  prod_info_dma_handle,
				  sizeof (megaRaidProductInfo),
				  PCI_DMA_FROMDEVICE);
	}

	/*
	 * kernel scans the channels from 0 to <= max_channel
	 */
	adapter->host->max_channel =
		adapter->productInfo.SCSIChanPresent + NVIRT_CHAN -1;

	adapter->host->max_id = 16;	/* max targets per channel */
	adapter->host->max_lun = 7;	/* Upto 7 luns for non disk devices */
	adapter->host->cmd_per_lun = MAX_CMD_PER_LUN;
	adapter->numldrv = enquiry3Pnt->numLDrv;
	adapter->max_cmds = adapter->productInfo.MaxConcCmds;
	if (adapter->max_cmds > MAX_COMMANDS)
		adapter->max_cmds = MAX_COMMANDS - 1;

	adapter->host->can_queue = adapter->max_cmds - 1;

	/* use HP firmware and bios version encoding */
	if (adapter->productInfo.subSystemVendorID == HP_SUBSYS_ID) {
		sprintf (adapter->fwVer, "%c%d%d.%d%d",
			 adapter->productInfo.FwVer[2],
			 adapter->productInfo.FwVer[1] >> 8,
			 adapter->productInfo.FwVer[1] & 0x0f,
			 adapter->productInfo.FwVer[0] >> 8,
			 adapter->productInfo.FwVer[0] & 0x0f);
		sprintf (adapter->biosVer, "%c%d%d.%d%d",
			 adapter->productInfo.BiosVer[2],
			 adapter->productInfo.BiosVer[1] >> 8,
			 adapter->productInfo.BiosVer[1] & 0x0f,
			 adapter->productInfo.BiosVer[0] >> 8,
			 adapter->productInfo.BiosVer[0] & 0x0f);
	} else {
		memcpy(adapter->fwVer, (char *) adapter->productInfo.FwVer, 4);
		adapter->fwVer[4] = 0;

		memcpy(adapter->biosVer, (char *) adapter->productInfo.BiosVer, 4);
		adapter->biosVer[4] = 0;
	}
	adapter->support_ext_cdb = mega_support_ext_cdb(adapter);

	printk(KERN_NOTICE "megaraid: [%s:%s] detected %d logical drives" M_RD_CRLFSTR,
		adapter->fwVer, adapter->biosVer, adapter->numldrv);

	if (adapter->support_ext_cdb)
		printk(KERN_NOTICE "megaraid: supports extended CDBs.\n");

	/*
	 * I hope that I can unmap here, reason DMA transaction is not
	 * required any more after this
	 */

	return 0;
}

/*-------------------------------------------------------------------------
 *
 *                      Driver interface functions
 *
 *-------------------------------------------------------------------------*/

/*----------------------------------------------------------
 * Returns data to be displayed in /proc/scsi/megaraid/X
 *----------------------------------------------------------*/

int megaraid_proc_info (char *buffer, char **start, off_t offset,
		    int length, int host_no, int inout)
{
	*start = buffer;
	return 0;
}

static int mega_findCard (Scsi_Host_Template * pHostTmpl,
	       u16 pciVendor, u16 pciDev, long flag)
{
	u_char pciBus, pciDevFun, irq;
	struct Scsi_Host *host;
	adapter_t *adapter;
	u32 magic64;
	u16 magic;
	int		i, j;
#if BITS_PER_LONG == 64
	u64 megaBase;
#else
	u32 megaBase;
#endif
	u16 pciIdx = 0;
	u16 numFound = 0;
	u16 subsysid, subsysvid;
	struct pci_dev *pdev = NULL;

	while ((pdev = pci_find_device (pciVendor, pciDev, pdev))) {
		if (pci_enable_device (pdev))
			continue;
		pciBus = pdev->bus->number;
		pciDevFun = pdev->devfn;
		if ((flag & BOARD_QUARTZ) && (skip_id == -1)) {

			if( (pciVendor == PCI_VENDOR_ID_PERC4_DI_YSTONE &&
				pciDev == PCI_DEVICE_ID_PERC4_DI_YSTONE) ||
				(pciVendor == PCI_VENDOR_ID_PERC4_QC_VERDE &&
				pciDev == PCI_DEVICE_ID_PERC4_QC_VERDE) ) {

				flag |= BOARD_64BIT;
			}
			else {
				pci_read_config_word (pdev, PCI_CONF_AMISIG, &magic);
				if ((magic != AMI_SIGNATURE)
					&& (magic != AMI_SIGNATURE_471)) {
					pciIdx++;
					continue;	/* not an LSI board */
				}
				pci_read_config_dword (pdev, PCI_CONF_AMISIG64, &magic64);

				if (magic64 == AMI_64BIT_SIGNATURE)
					flag |= BOARD_64BIT;
			}
		}

		/* Hmmm...Should we not make this more modularized so that in future
		   we dont add for each firmware */

		if (flag & BOARD_QUARTZ) {
			/* Check to see if this is a Dell PERC RAID controller model 466 */
			pci_read_config_word(pdev, PCI_SUBSYSTEM_VENDOR_ID, &subsysvid);
			pci_read_config_word(pdev, PCI_SUBSYSTEM_ID, &subsysid);
		}

		printk(KERN_NOTICE
			"megaraid: found 0x%4.04x:0x%4.04x:idx %d:bus %d:slot %d:func %d\n",
			pciVendor, pciDev, pciIdx, pciBus, PCI_SLOT (pciDevFun),
			PCI_FUNC (pciDevFun));
		/* Read the base port and IRQ from PCI */
		megaBase = pci_resource_start (pdev, 0);
		irq = pdev->irq;

		pciIdx++;

		if (flag & BOARD_QUARTZ) {
			megaBase &= PCI_BASE_ADDRESS_MEM_MASK;
			megaBase = (long) ioremap (megaBase, 128);
			if (!megaBase)
				continue;
		} else {
			megaBase &= PCI_BASE_ADDRESS_IO_MASK;
			megaBase += 0x10;
		}

		/* Initialize SCSI Host structure */
		host = scsi_register (pHostTmpl, sizeof (adapter_t));
		if (!host)
			goto err_unmap;

		scsi_set_pci_device(host, pdev);
		adapter = (adapter_t *) host->hostdata;
		memset(adapter, 0, sizeof (adapter_t));

		printk(KERN_NOTICE "scsi%d : Found a MegaRAID controller at 0x%x, IRQ: %d"
			M_RD_CRLFSTR, host->host_no, (u_int) megaBase, irq);

		if (flag & BOARD_64BIT)
			printk(KERN_NOTICE "scsi%d : Enabling 64 bit support\n",
				host->host_no);

		/* Copy resource info into structure */
		INIT_LIST_HEAD(&adapter->free_list);
		INIT_LIST_HEAD(&adapter->pending_list);
		INIT_LIST_HEAD(&adapter->issued_list);
		INIT_LIST_HEAD(&adapter->completed_list);
		adapter->flag = flag;
		spin_lock_init(&adapter->lock);
		host->lock = &adapter->lock;

		adapter->dev = pdev;
		adapter->host = host;
		adapter->base = megaBase;
		adapter->host->irq = irq;
		adapter->host->io_port = megaBase;
		adapter->host->n_io_port = 16;
		adapter->host->unique_id = (pciBus << 8) | pciDevFun;

		adapter->mega_buffer = kmalloc(MEGA_BUFFER_SIZE, GFP_KERNEL);
		if (!adapter->mega_buffer) {
			printk(KERN_WARNING "megaraid: out of RAM.\n");
			goto err_release;
		}
		for (i = 0; i < MAX_COMMANDS; i++) {
			adapter->scbList[i] = (scb_t *)kmalloc(sizeof(scb_t*) *
									 MAX_COMMANDS, GFP_KERNEL);
			if (!adapter->scbList[i]) {
				printk(KERN_WARNING "megaraid: out of RAM.\n");
				goto err_release;
			}
		}
		megaCtlrs[numCtlrs] = adapter;

		if (!(flag & BOARD_QUARTZ)) {
			/* Request our IO Range */
			if( !request_region(megaBase, 16, "megaraid") )
				goto err_unregister;
		}

		/* Request our IRQ */
		if (request_irq (irq, megaraid_isr, SA_SHIRQ, "megaraid", adapter)) {
			printk(KERN_WARNING "megaraid: Couldn't register IRQ %d!\n", irq);
			goto err_release;
		}

		/*
		 * unmap while releasing the driver, Is it required to be 
		 * PCI_DMA_BIDIRECTIONAL 
		*/

		adapter->mailbox64ptr = pci_alloc_consistent (adapter->dev,
								    sizeof (mbox64_t),
								    &adapter->dma_handle64);

		mega_register_mailbox (adapter,adapter->dma_handle64);

		mega_i_query_adapter (adapter);

		if ((subsysid == 0x1111) && (subsysvid == 0x1111)) {
			/*
			 * Which firmware
			 */
			if (!strcmp(adapter->fwVer, "3.00") ||
					!strcmp(adapter->fwVer, "3.01")) {

				printk( KERN_WARNING
					"megaraid: Your  card is a Dell PERC 2/SC RAID controller "
					"with  firmware\nmegaraid: 3.00 or 3.01.  This driver is "
					"known to have corruption issues\nmegaraid: with those "
					"firmware versions on this specific card.  In order\n"
					"megaraid: to protect your data, please upgrade your "
					"firmware to version\nmegaraid: 3.10 or later, available "
					"from the Dell Technical Support web\nmegaraid: site at\n"
					"http://support.dell.com/us/en/filelib/download/"
					"index.asp?fileid=2940\n"
				);
			}
		}

		/*
		 * If we have a HP 1M(0x60E7)/2M(0x60E8) controller with
		 * firmware H.01.07, H.01.08, or H.01.09,  disable 64 bit support,
		 * since this firmware cannot handle 64 bit addressing
		 */
		if ((subsysvid == HP_SUBSYS_ID) &&
				((subsysid == 0x60E7)||(subsysid == 0x60E8)) ) {

			/*
			 * which firmware
			 */
			if( strcmp(adapter->fwVer, "H01.07") == 0 || 
			    strcmp(adapter->fwVer, "H01.08") == 0 ||
			    strcmp(adapter->fwVer, "H01.09") == 0 ) {
				printk(KERN_WARNING
						"megaraid: Firmware H.01.07/8/9 on 1M/2M "
						"controllers\nmegaraid: do not support 64 bit "
						"addressing.\n"
						"megaraid: DISABLING 64 bit support.\n");
				adapter->flag &= ~BOARD_64BIT;
			}
		}

		if (mega_is_bios_enabled (adapter))
			mega_hbas[numCtlrs].is_bios_enabled = 1;

		/*
		 * Find out which channel is raid and which is scsi
		 */
		mega_enum_raid_scsi(adapter);

		/*
		 * Find out if a logical drive is set as the boot drive. If there is
		 * one, will make that as the first logical drive.
		 * ROMB: Do we have to boot from a physical drive. Then all the
		 * physical drives would appear before the logical disks. Else, all
		 * the physical drives would be exported to the mid layer after
		 * logical disks.
		 */
		mega_get_boot_drv(adapter);

		if( ! adapter->boot_pdrv_enabled ) {
			for( i = 0; i < NVIRT_CHAN; i++ )
				adapter->logdrv_chan[i] = 1;

			for( i = NVIRT_CHAN; i < MAX_CHANNEL + NVIRT_CHAN; i++ )
				adapter->logdrv_chan[i] = 0;

			adapter->mega_ch_class <<= NVIRT_CHAN;
		}
		else {
			j = adapter->productInfo.SCSIChanPresent;
			for( i = 0; i < j; i++ )
				adapter->logdrv_chan[i] = 0;

			for( i = j; i < NVIRT_CHAN + j; i++ )
				adapter->logdrv_chan[i] = 1;
		}


		mega_hbas[numCtlrs].hostdata_addr = adapter;

		/*
		 * Do we support random deletion and addition of logical drives
		 */
		adapter->read_ldidmap = 0;	/* set it after first logdrv delete cmd */
		adapter->support_random_del = mega_support_random_del(adapter);

		/* Initialize SCBs */
		if (mega_init_scb (adapter)) {
			pci_free_consistent (adapter->dev,
					     sizeof (mbox64_t),
					     (void *) adapter->mailbox64ptr,
					     adapter->dma_handle64);
			scsi_unregister (host);
			continue;
		}

		/*
		 * Reset the pending commands counter
		 */
		atomic_set(&adapter->pend_cmds, 0);

		/*
		 * Fill in the structure which needs to be passed back to the
		 * application when it does an ioctl() for controller related
		 * information.
		 */

		i = numCtlrs;
		numCtlrs++;

		mcontroller[i].base = megaBase;
		mcontroller[i].irq = irq;
		mcontroller[i].numldrv = adapter->numldrv;
		mcontroller[i].pcibus = pciBus;
		mcontroller[i].pcidev = pciDev;
		mcontroller[i].pcifun = PCI_FUNC (pciDevFun);
		mcontroller[i].pciid = pciIdx;
		mcontroller[i].pcivendor = pciVendor;
		mcontroller[i].pcislot = PCI_SLOT (pciDevFun);
		mcontroller[i].uid = (pciBus << 8) | pciDevFun;

		numFound++;

		/* Set the Mode of addressing to 64 bit */
		if (adapter->flag & BOARD_64BIT) { 
			pci_set_dma_mask(pdev, 0xffffffffffffffffULL); 
			pci_set_consistent_dma_mask(pdev, 0xffffffffffffffffULL); 
		}
		continue;
err_release:
		if (adapter->mega_buffer)
			kfree((void *)adapter->mega_buffer);
		for (i = 0; i < MAX_COMMANDS; i++)
			if (adapter->scbList[i])
				kfree (adapter->scbList[i]);
		if (flag & BOARD_QUARTZ)
			release_region (megaBase, 16);
err_unregister:
		scsi_unregister (host);
err_unmap:
		if (flag & BOARD_QUARTZ)
			iounmap((void *) megaBase);
	}
	return numFound;
}

/*---------------------------------------------------------
 * Detects if a megaraid controller exists in this system
 *---------------------------------------------------------*/

int megaraid_detect (Scsi_Host_Template * pHostTmpl)
{
	int ctlridx = 0, count = 0;
	pHostTmpl->proc_name = "megaraid";

	skip_id = -1;
	if (megaraid && !strncmp(megaraid, "skip", strlen ("skip"))) {
		if (megaraid[4] != '\0') {
			skip_id = megaraid[4] - '0';
			if (megaraid[5] != '\0') {
				skip_id = (skip_id * 10) + (megaraid[5] - '0');
			}
		}
		skip_id = (skip_id > 15) ? -1 : skip_id;
	}

	printk(KERN_NOTICE "megaraid: " MEGARAID_VERSION);

	memset(mega_hbas, 0, sizeof (mega_hbas));

	/* Detect ROMBs first */
	count += mega_findCard (pHostTmpl, PCI_VENDOR_ID_DISCOVERY,
				PCI_DEVICE_ID_DISCOVERY, BOARD_QUARTZ);
	count += mega_findCard (pHostTmpl, PCI_VENDOR_ID_PERC4_DI_YSTONE,
				PCI_DEVICE_ID_PERC4_DI_YSTONE, BOARD_QUARTZ);
	/* Then detect cards based on date they were produced, oldest first */
	count += mega_findCard (pHostTmpl, PCI_VENDOR_ID_AMI,
				PCI_DEVICE_ID_AMI_MEGARAID, 0);
	count += mega_findCard (pHostTmpl, PCI_VENDOR_ID_AMI,
				PCI_DEVICE_ID_AMI_MEGARAID2, 0);
	count += mega_findCard (pHostTmpl, 0x8086,
				PCI_DEVICE_ID_AMI_MEGARAID3, BOARD_QUARTZ);
	count += mega_findCard (pHostTmpl, PCI_VENDOR_ID_AMI,
				PCI_DEVICE_ID_AMI_MEGARAID3, BOARD_QUARTZ);
	count += mega_findCard (pHostTmpl, PCI_VENDOR_ID_LSI_LOGIC,
				PCI_DEVICE_ID_AMI_MEGARAID3, BOARD_QUARTZ);
	count += mega_findCard (pHostTmpl, PCI_VENDOR_ID_PERC4_QC_VERDE,
				PCI_DEVICE_ID_PERC4_QC_VERDE, BOARD_QUARTZ);

	mega_reorder_hosts ();

#ifdef CONFIG_PROC_FS
	if (count) {
		mega_proc_dir_entry = proc_mkdir ("megaraid", &proc_root);
		if (!mega_proc_dir_entry)
			printk("megaraid: failed to create megaraid root\n");
		else
			for (ctlridx = 0; ctlridx < count; ctlridx++)
				mega_create_proc_entry (ctlridx,
							mega_proc_dir_entry);
	}
#endif

	/*
	 * Register the driver as a character device, for applications to access
	 * it for ioctls.
	 * Ideally, this should go in the init_module() routine, but since it is
	 * hidden in the file "scsi_module.c" ( included in the end ), we define
	 * it here
	 * First argument (major) to register_chrdev implies a dynamic major
	 * number allocation.
	 */
	if (count) {
		major = register_chrdev (0, "megadev", &megadev_fops);

		/*
		 * Register the Shutdown Notification hook in kernel
		 */
		if (register_reboot_notifier (&mega_notifier))
			printk("MegaRAID Shutdown routine not registered!!\n");

		init_MUTEX (&mimd_entry_mtx);
	}

	return count;
}

/*---------------------------------------------------------------------
 * Release the controller's resources
 *---------------------------------------------------------------------*/
int megaraid_release (struct Scsi_Host *pSHost)
{
	adapter_t *adapter;
	mbox_t *mbox;
	u_char raw_mbox[16];
	int i;

	adapter = (adapter_t *) pSHost->hostdata;
	mbox = (mbox_t *) raw_mbox;

	/* Flush cache to disk */
	memset(mbox, 0, 16);
	raw_mbox[0] = 0xA;

	irq_disable(adapter);
	free_irq (adapter->host->irq, adapter);
	/* Issue a blocking (interrupts disabled) command to the card */
	issue_scb_block(adapter, raw_mbox);

	/* Free our resources */
	if (adapter->flag & BOARD_QUARTZ)
		iounmap((void *) adapter->base);
	else
		release_region(adapter->host->io_port, 16);

	mega_freeSgList (adapter);
	pci_free_consistent (adapter->dev,
			     sizeof (mbox64_t),
			     (void *) adapter->mailbox64ptr,
			     adapter->dma_handle64);

#ifdef CONFIG_PROC_FS
	if (adapter->controller_proc_dir_entry) {
		remove_proc_entry ("stat", adapter->controller_proc_dir_entry);
		remove_proc_entry ("status", adapter->controller_proc_dir_entry);
		remove_proc_entry ("config", adapter->controller_proc_dir_entry);
		remove_proc_entry ("mailbox", adapter->controller_proc_dir_entry);
		for (i = 0; i < numCtlrs; i++) {
			char buf[12] = { 0 };
			sprintf (buf, "%d", i);
			remove_proc_entry (buf, mega_proc_dir_entry);
		}
		remove_proc_entry ("megaraid", &proc_root);
	}
#endif
	kfree((void *)adapter->mega_buffer);
	for (i = 0; i < MAX_COMMANDS; i++)
		if (adapter->scbList[i])
			kfree (adapter->scbList[i]);

	/*
	 *	Release the controller memory. A word of warning this frees
	 *	hostdata and that includes adapter-> so be careful what you
	 *	dereference beyond this point
	 */
	scsi_unregister (pSHost);

	/*
	 * Unregister the character device interface to the driver. Ideally this
	 * should have been done in cleanup_module routine. Since this is hidden
	 * in file "scsi_module.c", we do it here.
	 * major is the major number of the character device returned by call to
	 * register_chrdev() routine.
	 */
	unregister_chrdev(major, "megadev");
	unregister_reboot_notifier(&mega_notifier);

	return 0;
}

static int mega_is_bios_enabled (adapter_t *adapter)
{
	unsigned char raw_mbox[16];
	mbox_t *mbox;
	int ret;

	mbox = (mbox_t *) raw_mbox;

	memset(mbox, 0, sizeof (mbox));
	memset((void *) adapter->mega_buffer, 0, sizeof (adapter->mega_buffer));

	/*
	 * issue command to find out if the BIOS is enabled for this controller
	 */
	raw_mbox[0] = IS_BIOS_ENABLED;
	raw_mbox[2] = GET_BIOS;

	mbox->xferaddr = virt_to_bus ((void *) adapter->mega_buffer);

	ret = issue_scb_block(adapter, raw_mbox);

	return *(char *)adapter->mega_buffer;
}

/*
 * Find out what channels are RAID/SCSI
 */
void mega_enum_raid_scsi(adapter_t *adapter)
{
	unsigned char raw_mbox[16];
	dma_addr_t dma_handle;
	mbox_t *mbox;
	int i;

	mbox = (mbox_t *)raw_mbox;

	memset(mbox, 0, sizeof(mbox));
	/*
	 * issue command to find out what channels are raid/scsi
	 */
	raw_mbox[0] = CHNL_CLASS;
	raw_mbox[2] = GET_CHNL_CLASS;

	memset((void *)adapter->mega_buffer, 0, sizeof(adapter->mega_buffer));

	dma_handle = pci_map_single(adapter->dev, (void *)adapter->mega_buffer,
			      (2 * 1024L), PCI_DMA_FROMDEVICE);

	mbox->xferaddr = dma_handle;

	/*
	 * Non-ROMB firware fail this command, so all channels
	 * must be shown RAID
	 */
	adapter->mega_ch_class = 0xFF;
	if (!issue_scb_block(adapter, raw_mbox)) {
		adapter->mega_ch_class = *((char *)adapter->mega_buffer);
	}

	for( i = 0; i < adapter->productInfo.SCSIChanPresent; i++ ) {
		if( (adapter->mega_ch_class >> i) & 0x01 )
			printk(KERN_NOTICE"megaraid: channel[%d] is raid.\n", i+1);
		else
			printk(KERN_NOTICE"megaraid: channel[%d] is scsi.\n", i+1);
	}


	pci_unmap_single(adapter->dev, dma_handle, (2 * 1024), PCI_DMA_FROMDEVICE);
}


/*
 * get the boot logical drive number if enabled
 */
void mega_get_boot_drv(adapter_t *adapter)
{
	mbox_t *mbox;
	unsigned char raw_mbox[16];
	struct private_bios_data *prv_bios_data;
	u16		cksum = 0;
	u8		*cksum_p;
	u8		boot_pdrv;
	int		i;
	dma_addr_t	dma_handle;

	mbox = (mbox_t *)raw_mbox;

	memset(mbox, 0, sizeof(mbox));

	raw_mbox[0] = BIOS_PVT_DATA;
	raw_mbox[2] = GET_BIOS_PVT_DATA;

	memset((void *)adapter->mega_buffer, 0, sizeof(adapter->mega_buffer));

	dma_handle = pci_map_single(adapter->dev, (void *)adapter->mega_buffer,
			      (2 * 1024L), PCI_DMA_FROMDEVICE);
	mbox->xferaddr = dma_handle;

	adapter->boot_ldrv_enabled = 0;
	adapter->boot_ldrv = 0;

	adapter->boot_pdrv_enabled = 0;
	adapter->boot_pdrv_ch = 0;
	adapter->boot_pdrv_tgt = 0;

	if (!issue_scb_block(adapter, raw_mbox)) {

		prv_bios_data = (struct private_bios_data *)adapter->mega_buffer;

		cksum = 0;
		cksum_p = (char *)prv_bios_data;
		for (i = 0; i < 14; i++ ) {
			cksum += (u16)(*cksum_p++);
		}

		if (prv_bios_data->cksum == (u16)(0-cksum) ) {

			/*
			 * If MSB is set, a physical drive is set as boot device
			 */
			if( prv_bios_data->boot_drv & 0x80 ) {
				adapter->boot_pdrv_enabled = 1;
				boot_pdrv = prv_bios_data->boot_drv & 0x7F;
				adapter->boot_pdrv_ch = boot_pdrv / 16;
				adapter->boot_pdrv_tgt = boot_pdrv % 16;
			}
			else {
				adapter->boot_ldrv_enabled = 1;
				adapter->boot_ldrv = prv_bios_data->boot_drv;
			}
		}
		else {
			printk(KERN_WARNING "megaraid warning: cksum mismatch %d, %d!\n",
				prv_bios_data->cksum, (u16)(0-cksum));
		}
	}
	pci_unmap_single(adapter->dev, dma_handle,
				  (2 * 1024L), PCI_DMA_FROMDEVICE);
}


static void mega_reorder_hosts (void)
{
	struct Scsi_Host *shpnt;
	struct Scsi_Host *shone;
	struct Scsi_Host *shtwo;
	adapter_t *boot_host;
	int i;

	/*
	 * Find the (first) host which has it's BIOS enabled
	 */
	boot_host = NULL;
	for (i = 0; i < MAX_CONTROLLERS; i++) {
		if (mega_hbas[i].is_bios_enabled) {
			boot_host = mega_hbas[i].hostdata_addr;
			break;
		}
	}

	if (!boot_host) {
		printk(KERN_WARNING "megaraid: no BIOS enabled.\n");
		return;
	}

	/*
	 * Traverse through the list of SCSI hosts for our HBA locations
	 */
	shone = shtwo = NULL;
	for (shpnt = scsi_hostlist; shpnt; shpnt = shpnt->next) {
		/* Is it one of ours? */
		for (i = 0; i < MAX_CONTROLLERS; i++) {
			if ((adapter_t *) shpnt->hostdata ==
			    mega_hbas[i].hostdata_addr) {
				/* Does this one has BIOS enabled */
				if (mega_hbas[i].hostdata_addr == boot_host) {

					/* Are we first */
					if (!shtwo)	/* Yes! */
						return;
					else	/* :-( */
						shone = shpnt;
				} else {
					if (!shtwo) {
						/* were we here before? xchng first */
						shtwo = shpnt;
					}
				}
				break;
			}
		}
		/*
		 * Have we got the boot host and one which does not have the bios
		 * enabled.
		 */
		if (shone && shtwo)
			break;
	}
	if (shone && shtwo) {
		mega_swap_hosts (shone, shtwo);
	}

	return;
}

static void mega_swap_hosts (struct Scsi_Host *shone, struct Scsi_Host *shtwo)
{
	struct Scsi_Host *prevtoshtwo;
	struct Scsi_Host *prevtoshone;
	struct Scsi_Host *save = NULL;;

	/* Are these two nodes adjacent */
	if (shtwo->next == shone) {

		if (shtwo == scsi_hostlist && !shone->next) {

			/* just two nodes */
			scsi_hostlist = shone;
			shone->next = shtwo;
			shtwo->next = NULL;
		} else if (shtwo == scsi_hostlist) {
			/* first two nodes of the list */

			scsi_hostlist = shone;
			shtwo->next = shone->next;
			scsi_hostlist->next = shtwo;
		} else if (!shone->next) {
			/* last two nodes of the list */

			prevtoshtwo = scsi_hostlist;

			while (prevtoshtwo->next != shtwo)
				prevtoshtwo = prevtoshtwo->next;

			prevtoshtwo->next = shone;
			shone->next = shtwo;
			shtwo->next = NULL;
		} else {
			prevtoshtwo = scsi_hostlist;

			while (prevtoshtwo->next != shtwo)
				prevtoshtwo = prevtoshtwo->next;

			prevtoshtwo->next = shone;
			shtwo->next = shone->next;
			shone->next = shtwo;
		}

	} else if (shtwo == scsi_hostlist && !shone->next) {
		/* shtwo at head, shone at tail, not adjacent */

		prevtoshone = scsi_hostlist;

		while (prevtoshone->next != shone)
			prevtoshone = prevtoshone->next;

		scsi_hostlist = shone;
		shone->next = shtwo->next;
		prevtoshone->next = shtwo;
		shtwo->next = NULL;
	} else if (shtwo == scsi_hostlist && shone->next) {
		/* shtwo at head, shone is not at tail */

		prevtoshone = scsi_hostlist;
		while (prevtoshone->next != shone)
			prevtoshone = prevtoshone->next;

		scsi_hostlist = shone;
		prevtoshone->next = shtwo;
		save = shtwo->next;
		shtwo->next = shone->next;
		shone->next = save;
	} else if (!shone->next) {
		/* shtwo not at head, shone at tail */

		prevtoshtwo = scsi_hostlist;
		prevtoshone = scsi_hostlist;

		while (prevtoshtwo->next != shtwo)
			prevtoshtwo = prevtoshtwo->next;
		while (prevtoshone->next != shone)
			prevtoshone = prevtoshone->next;

		prevtoshtwo->next = shone;
		shone->next = shtwo->next;
		prevtoshone->next = shtwo;
		shtwo->next = NULL;

	} else {
		prevtoshtwo = scsi_hostlist;
		prevtoshone = scsi_hostlist;
		save = NULL;;

		while (prevtoshtwo->next != shtwo)
			prevtoshtwo = prevtoshtwo->next;
		while (prevtoshone->next != shone)
			prevtoshone = prevtoshone->next;

		prevtoshtwo->next = shone;
		save = shone->next;
		shone->next = shtwo->next;
		prevtoshone->next = shtwo;
		shtwo->next = save;
	}
	return;
}

static inline void mega_freeSgList (adapter_t *adapter)
{
	int i;

	for (i = 0; i < adapter->max_cmds; i++) {
		if (adapter->scbList[i]->sgList)
			pci_free_consistent (adapter->dev,
					     sizeof (mega_64sglist) *
					     MAX_SGLIST,
					     adapter->scbList[i]->sgList,
					     adapter->scbList[i]->dma_sghandle64);
	}
}

/*----------------------------------------------
 * Get information about the card/driver
 *----------------------------------------------*/
const char *megaraid_info (struct Scsi_Host *pSHost)
{
	static char buffer[512];
	adapter_t *adapter;

	adapter = (adapter_t *) pSHost->hostdata;

	sprintf (buffer,
		 "LSI Logic MegaRAID %s %d commands %d targs %d chans %d luns",
		 adapter->fwVer, adapter->productInfo.MaxConcCmds,
		 adapter->host->max_id, adapter->host->max_channel,
		 adapter->host->max_lun);
	return buffer;
}

/*-----------------------------------------------------------------
 * Perform a SCSI command
 * Mailbox area:
 *   00 01 command
 *   01 01 command id
 *   02 02 # of sectors
 *   04 04 logical bus address
 *   08 04 physical buffer address
 *   0C 01 logical drive #
 *   0D 01 length of scatter/gather list
 *   0E 01 reserved
 *   0F 01 mailbox busy
 *   10 01 numstatus byte
 *   11 01 status byte
 *-----------------------------------------------------------------*/
int megaraid_queue (Scsi_Cmnd *cmd, void (*pktComp) (Scsi_Cmnd *))
{
	scb_t *scb;
	char *user_area = NULL;
	adapter_t *adapter;

	adapter = (adapter_t *) cmd->host->hostdata;

#if 0
	if (!(adapter->flag & (1L << cmd->channel))) {
		if (cmd->channel < adapter->productInfo.SCSIChanPresent)
			printk(KERN_NOTICE "scsi%d: scanning channel %d for devices.\n",
				adapter->host->host_no, cmd->channel);
		else
			printk(KERN_NOTICE
				"scsi%d: scanning virtual channel %d for logical drives.\n",
				adapter->host->host_no,
				cmd->channel-adapter->productInfo.SCSIChanPresent+1);

		adapter->flag |= (1L << cmd->channel);
	}
#endif

	cmd->scsi_done = pktComp;

	if (mega_driver_ioctl(adapter, cmd))
		return 0;

	/* If driver in abort or reset.. cancel this command */
	if (adapter->flag & (IN_ABORT|IN_RESET)) {
		if (adapter->flag & IN_ABORT)
			cmd->result = (DID_ABORT << 16);
		else
			cmd->result = (DID_RESET << 16);
		/* Add Scsi_Command to end of completed queue */
		list_add_tail(SCSI_LIST(cmd), &adapter->completed_list);
		return 0;
	}

	/* Allocate and build a SCB request */
	if ((scb = mega_build_cmd (adapter, cmd))) {

		list_add_tail(&scb->list, &adapter->pending_list);
		/*
		 * Check if the HBA is in quiescent state, e.g., during a delete
		 * logical drive opertion. If it is, do not issue any more commands
		 */
		if (!adapter->quiescent) {
			/* Add SCB to the head of the pending queue */
			mega_runpendq (adapter);
			return 0;
		}

		if (scb->cmd->cmnd[0] == M_RD_IOCTL_CMD_NEW) {
			init_MUTEX_LOCKED (&scb->ioctl_sem);
			spin_unlock_irq (&adapter->lock);
			down (&scb->ioctl_sem);
    		user_area = (char *)*((u32*)&scb->cmd->cmnd[4]);
			if (copy_to_user
			    (user_area, scb->buff_ptr, scb->iDataSize)) {
				printk
				    ("megaraid: Error copying ioctl return value to user buffer.\n");
				scb->cmd->result = (DID_ERROR << 16);
			}
			spin_lock_irq (&adapter->lock);
			kfree (scb->buff_ptr);
			scb->buff_ptr = NULL;
			mega_cmd_done (adapter, scb, scb->cmd->result);
			mega_rundoneq (adapter);
			mega_runpendq (adapter);
		}
	}
	return 0;
}

/*----------------------------------------------------------------------
 * Issue a blocking command to the controller
 *----------------------------------------------------------------------*/
volatile static int internal_done_flag = 0;
volatile static int internal_done_errcode = 0;

static DECLARE_WAIT_QUEUE_HEAD (internal_wait);

static void internal_done (Scsi_Cmnd *cmd)
{
	internal_done_errcode = cmd->result;
	internal_done_flag++;
	wake_up (&internal_wait);
}

/* shouldn't be used, but included for completeness */

int megaraid_command (Scsi_Cmnd *cmd)
{
	internal_done_flag = 0;

	/* Queue command, and wait until it has completed */
	megaraid_queue (cmd, internal_done);

	while (!internal_done_flag)
		interruptible_sleep_on (&internal_wait);

	return internal_done_errcode;
}

/*---------------------------------------------------------------------
 * Abort a previous SCSI request
 *---------------------------------------------------------------------*/
int megaraid_abort (Scsi_Cmnd *cmd)
{
	adapter_t *adapter;
	scb_t *scb;
	list_t *pos, *next;
	int rc;

	rc = SCSI_ABORT_NOT_RUNNING;

	adapter = (adapter_t *) cmd->host->hostdata;

	adapter->flag |= IN_ABORT;

	list_for_each_safe(pos, next, &adapter->pending_list) {
		scb = list_entry(pos, scb_t, list);
		if (scb->cmd == cmd) {
			/* Found an aborting command */
	/*
	 * If the command is queued to be issued to the firmware, abort the scsi cmd,
	 * If the command is already aborted in a previous call to the _abort entry
	 *  point, return SCSI_ABORT_SNOOZE, suggesting a reset.
	 * If the command is issued to the firmware, which might complete after
	 *  some time, we will mark the scb as aborted, and return to the mid layer,
	 *  that abort could not be done.
	 *  In the ISR, when this command actually completes, we will perform a normal
	 *  completion.
	 *
	 * Oct 27, 1999
	 */

			switch (scb->state) {
			case SCB_ABORTED:	/* Already aborted */
				rc = SCSI_ABORT_SNOOZE;
				break;
			case SCB_ISSUED:	/* Waiting on ISR result */
				rc = SCSI_ABORT_NOT_RUNNING;
				scb->state = SCB_ABORTED;
				break;
			case SCB_ACTIVE:	/* still on the pending queue */
				mega_freeSCB (adapter, scb);
				cmd->result = (DID_ABORT << 16);
				list_add_tail(SCSI_LIST(cmd), &adapter->completed_list);
				rc = SCSI_ABORT_SUCCESS;
				break;
			default:
				printk
				    ("megaraid_abort: unknown command state!!\n");
				rc = SCSI_ABORT_NOT_RUNNING;
				break;
			}
			break;
		}
	}

	adapter->flag &= ~IN_ABORT;

	/*
	 * This is required here to complete any completed requests
	 * to be communicated over to the mid layer.
	 */
	mega_rundoneq (adapter);

	return rc;
}

/*---------------------------------------------------------------------
 * Reset a previous SCSI request
 *---------------------------------------------------------------------*/

int megaraid_reset (Scsi_Cmnd *cmd, unsigned int flags)
{
	adapter_t *adapter;
	int idx, rc;
	scb_t *scb;

	rc = SCSI_RESET_NOT_RUNNING;
	adapter = (adapter_t *) cmd->host->hostdata;

	adapter->flag |= IN_RESET;

	printk("megaraid_RESET: %.08lx cmd=%.02x <c=%d.t=%d.l=%d>, flag = %x\n",
	     cmd->serial_number, cmd->cmnd[0], cmd->channel,
	     cmd->target, cmd->lun, flags);

	/*
	 * Walk list of SCBs for any that are still outstanding
	 */
	for (idx = 0; idx < adapter->max_cmds; idx++) {
		if (adapter->scbList[idx]->state != SCB_FREE) {
			cmd = adapter->scbList[idx]->cmd;
			scb = adapter->scbList[idx];
			if (cmd) {
				scb->state = SCB_RESET;
				break;
			}
		}
	}
	adapter->flag &= ~IN_RESET;
	mega_rundoneq (adapter);
	return rc;
}

#ifdef CONFIG_PROC_FS
/* Following code handles /proc fs  */
static int proc_printf (adapter_t *adapter, const char *fmt, ...)
{
	va_list args;
	int i;

	if (adapter->procidx > PROCBUFSIZE)
		return 0;

	va_start(args, fmt);
	i = vsprintf((adapter->procbuf + adapter->procidx), fmt, args);
	va_end (args);

	adapter->procidx += i;
	return i;
}

static int proc_read_config (char *page, char **start, off_t offset,
		  int count, int *eof, void *data)
{

	adapter_t *adapter = (adapter_t *) data;

	*start = page;

	if (adapter->productInfo.ProductName[0])
		proc_printf (adapter, "%s\n", adapter->productInfo.ProductName);

	proc_printf (adapter, "Controller Type: ");

	if (adapter->flag & BOARD_QUARTZ)
		proc_printf (adapter, "438/466/467/471/493\n");
	else
		proc_printf (adapter, "418/428/434\n");

	if (adapter->flag & BOARD_40LD)
		proc_printf (adapter,
			     "Controller Supports 40 Logical Drives\n");

	if (adapter->flag & BOARD_64BIT)
		proc_printf (adapter,
			     "Controller / Driver uses 64 bit memory addressing\n");

	proc_printf (adapter, "Base = %08x, Irq = %d, ", adapter->base,
		     adapter->host->irq);

	proc_printf (adapter, "Logical Drives = %d, Channels = %d\n",
		     adapter->numldrv, adapter->productInfo.SCSIChanPresent);

	proc_printf (adapter, "Version =%s:%s, DRAM = %dMb\n",
		     adapter->fwVer, adapter->biosVer,
		     adapter->productInfo.DramSize);

	proc_printf (adapter,
		     "Controller Queue Depth = %d, Driver Queue Depth = %d\n",
		     adapter->productInfo.MaxConcCmds, adapter->max_cmds);
	COPY_BACK;
	return count;
}

static int proc_read_stat (char *page, char **start, off_t offset,
		int count, int *eof, void *data)
{
	int i;
	adapter_t *adapter = (adapter_t *) data;

	*start = page;

	proc_printf (adapter, "Statistical Information for this controller\n");

	for (i = 0; i < adapter->numldrv; i++) {
		proc_printf (adapter, "Logical Drive %d:\n", i);

#if STATS
		proc_printf (adapter,
			     "\tReads Issued = %lu, Writes Issued = %lu\n",
			     adapter->nReads[i], adapter->nWrites[i]);

		proc_printf (adapter,
			     "\tSectors Read = %lu, Sectors Written = %lu\n\n",
			     adapter->nReadBlocks[i], adapter->nWriteBlocks[i]);
#endif

	}

	COPY_BACK;
	return count;
}

static int proc_read_status (char *page, char **start, off_t offset,
		  int count, int *eof, void *data)
{
	adapter_t *adapter = (adapter_t *) data;
	*start = page;

	proc_printf (adapter, "TBD\n");
	COPY_BACK;
	return count;
}

static int proc_read_mbox (char *page, char **start, off_t offset,
		int count, int *eof, void *data)
{

	adapter_t *adapter = (adapter_t *) data;
	volatile mbox_t *mbox = adapter->mbox;

	*start = page;

	proc_printf (adapter, "Contents of Mail Box Structure\n");
	proc_printf (adapter, "  Fw Command   = 0x%02x\n", mbox->cmd);
	proc_printf (adapter, "  Cmd Sequence = 0x%02x\n", mbox->cmdid);
	proc_printf (adapter, "  No of Sectors= %04d\n", mbox->numsectors);
	proc_printf (adapter, "  LBA          = 0x%02x\n", mbox->lba);
	proc_printf (adapter, "  DTA          = 0x%08x\n", mbox->xferaddr);
	proc_printf (adapter, "  Logical Drive= 0x%02x\n", mbox->logdrv);
	proc_printf (adapter, "  No of SG Elmt= 0x%02x\n", mbox->numsgelements);
	proc_printf (adapter, "  Busy         = %01x\n", mbox->busy);
	proc_printf (adapter, "  Status       = 0x%02x\n", mbox->status);

	COPY_BACK;
	return count;
}

#define CREATE_READ_PROC(string, fxn) create_proc_read_entry(string, \
                                         S_IRUSR | S_IFREG,\
                                         controller_proc_dir_entry,\
                                         fxn, adapter)

static void mega_create_proc_entry (int index, struct proc_dir_entry *parent)
{
	u_char string[64] = { 0 };
	adapter_t *adapter = megaCtlrs[index];
	struct proc_dir_entry *controller_proc_dir_entry = NULL;

	sprintf (string, "%d", index);

	controller_proc_dir_entry =
	    adapter->controller_proc_dir_entry = proc_mkdir (string, parent);

	if (!controller_proc_dir_entry)
		printk("\nmegaraid: proc_mkdir failed\n");
	else {
		adapter->proc_read =
		    CREATE_READ_PROC ("config", proc_read_config);
		adapter->proc_status =
		    CREATE_READ_PROC ("status", proc_read_status);
		adapter->proc_stat = CREATE_READ_PROC ("stat", proc_read_stat);
		adapter->proc_mbox =
		    CREATE_READ_PROC ("mailbox", proc_read_mbox);
	}

}
#endif				/* CONFIG_PROC_FS */

/*-------------------------------------------------------------
 * Return the disk geometry for a particular disk
 * Input:
 *   Disk *disk - Disk geometry
 *   kdev_t dev - Device node
 *   int *geom  - Returns geometry fields
 *     geom[0] = heads
 *     geom[1] = sectors
 *     geom[2] = cylinders
 *-------------------------------------------------------------*/
int megaraid_biosparam (Disk * disk, kdev_t dev, int *geom)
{
	int heads, sectors, cylinders;
	adapter_t *adapter;

	/* Get pointer to host config structure */
	adapter = (adapter_t *) disk->device->host->hostdata;

	if (IS_RAID_CH(adapter, disk->device->channel)) {
			/* Default heads (64) & sectors (32) */
			heads = 64;
			sectors = 32;
			cylinders = disk->capacity / (heads * sectors);

			/* Handle extended translation size for logical drives > 1Gb */
			if (disk->capacity >= 0x200000) {
				heads = 255;
				sectors = 63;
				cylinders = disk->capacity / (heads * sectors);
			}

			/* return result */
			geom[0] = heads;
			geom[1] = sectors;
			geom[2] = cylinders;
	}
	else {
		if (!mega_partsize(disk, dev, geom))
			return 0;

		printk(KERN_WARNING
				"megaraid: invalid partition on this disk on channel %d\n",
				disk->device->channel);

		/* Default heads (64) & sectors (32) */
		heads = 64;
		sectors = 32;
		cylinders = disk->capacity / (heads * sectors);

		/* Handle extended translation size for logical drives > 1Gb */
		if (disk->capacity >= 0x200000) {
			heads = 255;
			sectors = 63;
			cylinders = disk->capacity / (heads * sectors);
		}

		/* return result */
		geom[0] = heads;
		geom[1] = sectors;
		geom[2] = cylinders;
	}

	return 0;
}

/*
 * Function : static int mega_partsize(Disk * disk, kdev_t dev, int *geom)
 *
 * Purpose : to determine the BIOS mapping used to create the partition
 *			table, storing the results (cyls, hds, and secs) in geom
 *
 * Note:	Code is picked from scsicam.h
 *
 * Returns : -1 on failure, 0 on success.
 */
static int mega_partsize(Disk * disk, kdev_t dev, int *geom)
{
	struct buffer_head *bh;
	struct partition *p, *largest = NULL;
	int i, largest_cyl;
	int heads, cyls, sectors;
	int capacity = disk->capacity;

	int ma = MAJOR(dev);
	int mi = (MINOR(dev) & ~0xf);

	int block = 1024; 

	if (blksize_size[ma])
		block = blksize_size[ma][mi];
		
	if (!(bh = bread(MKDEV(ma,mi), 0, block)))
		return -1;

	if (*(unsigned short *)(bh->b_data + 510) == 0xAA55 ) {

		for (largest_cyl = -1, p = (struct partition *)(0x1BE + bh->b_data),
				i = 0; i < 4; ++i, ++p) {

			if (!p->sys_ind) continue;

			cyls = p->end_cyl + ((p->end_sector & 0xc0) << 2);

			if (cyls >= largest_cyl) {
				largest_cyl = cyls;
				largest = p;
			}
		}
	}

	if (largest) {
		heads = largest->end_head + 1;
		sectors = largest->end_sector & 0x3f;

		if (!heads || !sectors) {
			brelse(bh);
			return -1;
		}

		cyls = capacity/(heads * sectors);

		geom[0] = heads;
		geom[1] = sectors;
		geom[2] = cyls;

		brelse(bh);
		return 0;
	}

	brelse(bh);
	return -1;
}


/*
 * This routine will be called when the use has done a forced shutdown on the
 * system. Flush the Adapter cache, that's the most we can do.
 */
static int megaraid_reboot_notify (struct notifier_block *this,
										unsigned long code, void *unused)
{
	adapter_t *adapter;
	struct Scsi_Host *pSHost;
	u_char raw_mbox[16];
	mbox_t *mbox;
	int i;

	if (code == SYS_DOWN || code == SYS_HALT) {
		for (i = 0; i < numCtlrs; i++) {
			pSHost = megaCtlrs[i]->host;

			adapter = (adapter_t *) pSHost->hostdata;
			mbox = (mbox_t *) raw_mbox;

			/* Flush cache to disk */
			memset(mbox, 0, 16);
			raw_mbox[0] = 0xA;

			irq_disable(adapter);
			free_irq(adapter->host->irq, adapter);

			/*
			 * Issue a blocking (interrupts disabled) command to
			 * the card
			 */
			issue_scb_block(adapter, raw_mbox);
		}
	}
	return NOTIFY_DONE;
}

static int mega_init_scb(adapter_t *adapter)
{
	int idx;

	for (idx = adapter->max_cmds - 1; idx >= 0; idx--) {

		adapter->scbList[idx]->idx = idx;

		/*
		 * ISR will make this flag zero to indicate the command has been
		 * completed. This is only for user ioctl calls. Rest of the driver
		 * and the mid-layer operations are not connected with this flag.
		 */
		adapter->scbList[idx]->sgList = pci_alloc_consistent (adapter->dev,
					  sizeof (mega_64sglist) * MAX_SGLIST,
					  &adapter->scbList[idx]->dma_sghandle64);

		adapter->scbList[idx]->sg64List =
		    (mega_64sglist *) adapter->scbList[idx]->sgList;

		if (!adapter->scbList[idx]->sgList) {
			printk(KERN_WARNING "Can't allocate sglist for id %d\n", idx);
			mega_freeSgList (adapter);
			return -1;
		}
		adapter->scbList[idx]->pthru = pci_alloc_consistent (adapter->dev,
					  sizeof (mega_passthru),
					  &adapter->scbList[idx]->dma_passthruhandle64);

		if (!adapter->scbList[idx]->pthru) {
			printk(KERN_WARNING "Can't allocate passthru for id %d\n", idx);
			mega_freeSgList (adapter);
			return -1;
		}

		adapter->scbList[idx]->epthru =
			pci_alloc_consistent(adapter->dev, sizeof(mega_ext_passthru),
				&adapter->scbList[idx]->dma_ext_passthruhandle64);

		if (!adapter->scbList[idx]->epthru)
			printk(KERN_WARNING
				"Can't allocate extended passthru for id %d\n", idx);
		/* 
		 * Allocate a 256 Byte Bounce Buffer for handling INQ/RD_CAPA 
		 */
		adapter->scbList[idx]->bounce_buffer =
			pci_alloc_consistent (adapter->dev,
								  256,
								  &adapter->scbList[idx]->dma_bounce_buffer);

		if (!adapter->scbList[idx]->bounce_buffer)
			printk("megaraid: allocation for bounce buffer failed\n");

		adapter->scbList[idx]->dma_type = M_RD_DMA_TYPE_NONE;

		if (idx < MAX_COMMANDS) {
			/*
			 * Link to free list
			 * lock not required since we are loading the driver, so no
			 * commands possible right now.
			 */
			adapter->scbList[idx]->state = SCB_FREE;
			adapter->scbList[idx]->cmd = NULL;
			list_add(&adapter->scbList[idx]->list, &adapter->free_list);
		}
	}
	return 0;
}

/*
 * Routines for the character/ioctl interface to the driver
 */
static int megadev_open (struct inode *inode, struct file *filep)
{
	MOD_INC_USE_COUNT;
	return 0;
}

static int megadev_ioctl_entry (struct inode *inode, struct file *filep,
		     unsigned int cmd, unsigned long arg)
{
	int ret = -1;

	/*
	 * We do not allow parallel ioctls to the driver as of now.
	 */
	down (&mimd_entry_mtx);
	ret = megadev_ioctl (inode, filep, cmd, arg);
	up (&mimd_entry_mtx);

	return ret;
}

static int megadev_ioctl (struct inode *inode, struct file *filep,
	       unsigned int cmd, unsigned long arg)
{
	int adapno;
	kdev_t dev;
	u32 inlen;
	struct uioctl_t ioc;
	char *kvaddr = NULL;
	int nadap = numCtlrs;
	u8 opcode;
	u32 outlen;
	int ret;
	u8 subopcode;
	Scsi_Cmnd *scsicmd;
	struct Scsi_Host *shpnt;
	char *uaddr;
	struct uioctl_t *uioc;
	dma_addr_t	dma_addr;
	u32		length;
	adapter_t *adapter = NULL;
	struct pci_dev pdev;
	struct pci_dev *pdevp = &pdev;
	unsigned long flags;

	if (!inode || !(dev = inode->i_rdev))
		return -EINVAL;

	if (_IOC_TYPE (cmd) != MEGAIOC_MAGIC)
		return (-EINVAL);

	/*
	 * Get the user ioctl structure
	 */
	ret = verify_area (VERIFY_WRITE, (char *) arg, sizeof (struct uioctl_t));

	if (ret)
		return ret;

	if (copy_from_user (&ioc, (char *) arg, sizeof (struct uioctl_t)))
		return -EFAULT;

	/*
	 * The first call the applications should make is to find out the
	 * number of controllers in the system. The next logical call should
	 * be for getting the list of controllers in the system as detected
	 * by the driver.
	 */

	/*
	 * Get the opcode and subopcode for the commands
	 */
	opcode = ioc.ui.fcs.opcode;
	subopcode = ioc.ui.fcs.subopcode;

	switch (opcode) {
	case M_RD_DRIVER_IOCTL_INTERFACE:
		switch (subopcode) {
		case MEGAIOC_QDRVRVER:	/* Query driver version */
			put_user(driver_ver, (u32 *) ioc.data);
			return 0;

		case MEGAIOC_QNADAP:	/* Get # of adapters */
			put_user(nadap, (int *) ioc.data);
			return nadap;

		case MEGAIOC_QADAPINFO:	/* Get adapter information */
			/*
			 * which adapter?
			 */
			adapno = ioc.ui.fcs.adapno;

			/*
			 * The adapter numbers do not start with 0, at least in
			 * the user space. This is just to make sure, 0 is not the
			 * default value which will refer to adapter 1. So the
			 * user needs to make use of macros MKADAP() and GETADAP()
			 * (See megaraid.h) while making ioctl() call.
			 */
			adapno = GETADAP (adapno);

			if (adapno >= numCtlrs)
				return (-ENODEV);

			ret = verify_area (VERIFY_WRITE,
					   ioc.data,
					   sizeof (struct mcontroller));
			if (ret)
				return ret;

			/*
			 * Copy struct mcontroller to user area
			 */
			copy_to_user (ioc.data,
				      mcontroller + adapno,
				      sizeof (struct mcontroller));
			return 0;

		default:
			return (-EINVAL);

		}		/* inner switch */
		break;

	case M_RD_IOCTL_CMD_NEW:

		/*
		 * Deletion of logical drives is only handled in 0x80 commands
		 */
		if (ioc.mbox[0] == FC_DEL_LOGDRV && ioc.mbox[2] == OP_DEL_LOGDRV ) {
			return -EINVAL;
		}

		/* which adapter?  */
		adapno = ioc.ui.fcs.adapno;

		/* See comment above: MEGAIOC_QADAPINFO */
		adapno = GETADAP(adapno);

		if (adapno >= numCtlrs)
			return(-ENODEV);

		length = ioc.ui.fcs.length;

		/* Check for zero length buffer or very large buffers */
		if (!length || length > 32*1024 )
			return -EINVAL;

		/* save the user address */
		uaddr = ioc.ui.fcs.buffer;

		/*
		 * For M_RD_IOCTL_CMD_NEW commands, the fields outlen and inlen of
		 * uioctl_t structure are treated as flags. If outlen is 1, the
		 * data is transferred from the device and if inlen is 1, the data
		 * is transferred to the device.
		 */
		outlen = ioc.outlen;
		inlen = ioc.inlen;

		if (outlen) {
			ret = verify_area(VERIFY_WRITE, (char *)ioc.ui.fcs.buffer, length);
			if (ret) return ret;
		}
		if (inlen) {
			ret = verify_area(VERIFY_READ, (char *) ioc.ui.fcs.buffer, length);
			if (ret) return ret;
		}

		/*
		 * Find this host
		 */
		for (shpnt = scsi_hostlist; shpnt; shpnt = shpnt->next ) {
			if (shpnt->hostdata == (unsigned long *)megaCtlrs[adapno] ) {
				adapter = (adapter_t *)shpnt->hostdata;
				break;
			}
		}
		if (!shpnt)
			return -ENODEV;

		scsicmd = (Scsi_Cmnd *)kmalloc(sizeof(Scsi_Cmnd), GFP_KERNEL);
		if (!scsicmd)
			return -ENOMEM;

		memset(scsicmd, 0, sizeof(Scsi_Cmnd));
		scsicmd->host = shpnt;

		if (outlen || inlen ) {
			pdevp = &pdev;
			memcpy(pdevp, adapter->dev, sizeof(struct pci_dev));
			pdevp->dma_mask = 0xffffffff;
			kvaddr = pci_alloc_consistent(pdevp, length, &dma_addr);

			if (!kvaddr) {
				printk(KERN_WARNING "megaraid:allocation failed\n");
				kfree(scsicmd);
				return -ENOMEM;
			}

			ioc.ui.fcs.buffer = kvaddr;

			if (inlen) {
				/* copyin the user data */
				copy_from_user(kvaddr, (char *)uaddr, length );
			}
		}

		scsicmd->cmnd[0] = MEGADEVIOC;
		scsicmd->request_buffer = &ioc;

		init_MUTEX_LOCKED(&mimd_ioctl_sem);

		spin_lock_irqsave(&adapter->lock, flags);
		megaraid_queue(scsicmd, megadev_ioctl_done);
		spin_unlock_irqrestore(&adapter->lock, flags);

		down(&mimd_ioctl_sem);

		if (!scsicmd->result && outlen ) {
			copy_to_user(uaddr, kvaddr, length);
		}

		/*
		 * copyout the result
		 */
		uioc = (struct uioctl_t *)arg;

		if (ioc.mbox[0] == MEGA_MBOXCMD_PASSTHRU ) {
			put_user( scsicmd->result, &uioc->pthru.scsistatus );
		} else {
			put_user(1, &uioc->mbox[16]);	/* numstatus */
			/* status */
			put_user (scsicmd->result, &uioc->mbox[17]);
		}

		if (kvaddr)
			pci_free_consistent(pdevp, length, kvaddr, dma_addr);
		kfree (scsicmd);

		/* restore the user address */
		ioc.ui.fcs.buffer = uaddr;

		return ret;

	case M_RD_IOCTL_CMD:
		/* which adapter?  */
		adapno = ioc.ui.fcs.adapno;

		/* See comment above: MEGAIOC_QADAPINFO */
		adapno = GETADAP (adapno);

		if (adapno >= numCtlrs)
			return (-ENODEV);

		/* save the user address */
		uaddr = ioc.data;
		outlen = ioc.outlen;
		inlen = ioc.inlen;

		if ((outlen >= IOCTL_MAX_DATALEN) || (inlen >= IOCTL_MAX_DATALEN))
			return (-EINVAL);

		if (outlen) {
			ret = verify_area (VERIFY_WRITE, ioc.data, outlen);
			if (ret) return ret;
		}
		if (inlen) {
			ret = verify_area (VERIFY_READ, ioc.data, inlen);
			if (ret) return ret;
		}

		/*
		 * Find this host
		 */
		for (shpnt = scsi_hostlist; shpnt; shpnt = shpnt->next ) {
			if (shpnt->hostdata == (unsigned long *)megaCtlrs[adapno] ) {
				adapter = (adapter_t *)shpnt->hostdata;
				break;
			}
		}
		if (!shpnt)
			 return -ENODEV;

		/*
		 * ioctls for deleting logical drives is a special case, so check
		 * for it first
		 */
		if (ioc.mbox[0] == FC_DEL_LOGDRV && ioc.mbox[2] == OP_DEL_LOGDRV ) {

			if (!adapter->support_random_del ) {
				printk("megaraid: logdrv delete on non supporting f/w.\n");
				return -EINVAL;
			}

			uioc = (struct uioctl_t *)arg;

			ret = mega_del_logdrv(adapter, ioc.mbox[3]);

			put_user(1, &uioc->mbox[16]);	/* numstatus */
			put_user(ret, &uioc->mbox[17]);	/* status */

			/* if deletion failed, let the user know by failing ioctl */
			return ret;
		}

		scsicmd = (Scsi_Cmnd *)kmalloc(sizeof(Scsi_Cmnd), GFP_KERNEL);
		if (!scsicmd)
				return -ENOMEM;

		memset(scsicmd, 0, sizeof(Scsi_Cmnd));
		scsicmd->host = shpnt;

		if (outlen || inlen) {
			pdevp = &pdev;
			memcpy(pdevp, adapter->dev, sizeof(struct pci_dev));
			pdevp->dma_mask = 0xffffffff;
			/*
			 * Allocate a page of kernel space.
			 */
			kvaddr = pci_alloc_consistent(pdevp, PAGE_SIZE, &dma_addr);

			if (!kvaddr) {
				printk(KERN_WARNING "megaraid: allocation failed\n");
				kfree(scsicmd);
				return -ENOMEM;
			}

			ioc.data = kvaddr;

			if (inlen) {
				if (ioc.mbox[0] == MEGA_MBOXCMD_PASSTHRU) {
					/* copyin the user data */
					copy_from_user (kvaddr, uaddr, ioc.pthru.dataxferlen);
				} else {
					copy_from_user (kvaddr, uaddr, inlen);
				}
			}
		}

		scsicmd->cmnd[0] = MEGADEVIOC;
		scsicmd->request_buffer = &ioc;

		init_MUTEX_LOCKED (&mimd_ioctl_sem);

		spin_lock_irqsave(&adapter->lock, flags);
		megaraid_queue (scsicmd, megadev_ioctl_done);
		spin_unlock_irqrestore(&adapter->lock, flags);

		down (&mimd_ioctl_sem);

		if (!scsicmd->result && outlen) {
			if (ioc.mbox[0] == MEGA_MBOXCMD_PASSTHRU) {
				copy_to_user (uaddr, kvaddr, ioc.pthru.dataxferlen);
			} else {
				copy_to_user (uaddr, kvaddr, outlen);
			}
		}

		/*
		 * copyout the result
		 */
		uioc = (struct uioctl_t *) arg;

		if (ioc.mbox[0] == MEGA_MBOXCMD_PASSTHRU) {
			put_user (scsicmd->result, &uioc->pthru.scsistatus);
		} else {
			put_user (1, &uioc->mbox[16]);	/* numstatus */
			put_user (scsicmd->result, &uioc->mbox[17]); /* status */
		}

		if (kvaddr)
			pci_free_consistent(pdevp, PAGE_SIZE, kvaddr, dma_addr );

		kfree (scsicmd);

		/* restore user pointer */
		ioc.data = uaddr;

		return ret;

	default:
		return (-EINVAL);

	}/* Outer switch */

	return 0;
}

static void megadev_ioctl_done(Scsi_Cmnd *cmd)
{
	up(&mimd_ioctl_sem);
}

static scb_t * megadev_doioctl(adapter_t *adapter, Scsi_Cmnd *cmd)
{
	u8 user_cmd;
	struct uioctl_t *ioc = NULL;
	mbox_t *mbox = NULL;
	mega_ioctl_mbox *mboxioc = NULL;
	struct mbox_passthru *mboxthru = NULL;
	scb_t *scb = NULL;
	mega_passthru *pthru = NULL;

	if (!(scb = mega_allocateSCB (adapter, cmd))) {
		cmd->result = (DID_ERROR << 16);
		cmd->scsi_done(cmd);
		return NULL;
	}

	ioc = (struct uioctl_t *) cmd->request_buffer;

	memcpy(scb->raw_mbox, ioc->mbox, sizeof (scb->raw_mbox));

	/* The generic mailbox */
	mbox = (mbox_t *) ioc->mbox;

	/*
	 * Get the user command
	 */
	user_cmd = ioc->mbox[0];

	switch (user_cmd) {
	case MEGA_MBOXCMD_PASSTHRU:
		/*
		   * prepare the SCB with information from the user ioctl structure
		 */
		pthru = scb->pthru;
		memcpy(pthru, &ioc->pthru, sizeof (mega_passthru));
		mboxthru = (struct mbox_passthru *) scb->raw_mbox;
		if (adapter->flag & BOARD_64BIT) {
			/* This is just a sample with one element 
			   * This if executes onlu on 2.4 kernels
			 */
			mboxthru->dataxferaddr = scb->dma_passthruhandle64;
			scb->sg64List[0].address =
			    pci_map_single (adapter->dev,
					    ioc->data,
					    4096, PCI_DMA_BIDIRECTIONAL);
			scb->sg64List[0].length = 4096;	// TODO: Check this
			pthru->dataxferaddr = scb->dma_sghandle64;
			pthru->numsgelements = 1;
			mboxthru->cmd = 0xC3;
		} else {
			mboxthru->dataxferaddr = scb->dma_passthruhandle64;
			pthru->dataxferaddr =
			    pci_map_single (adapter->dev,
					    ioc->data,
					    4096, PCI_DMA_BIDIRECTIONAL);
			pthru->numsgelements = 0;
		}

		pthru->reqsenselen = 14;
		break;

	default:		/* Normal command */
		mboxioc = (mega_ioctl_mbox *) scb->raw_mbox;

		if (ioc->ui.fcs.opcode == M_RD_IOCTL_CMD_NEW) {
			scb->buff_ptr = ioc->ui.fcs.buffer;
			scb->iDataSize = ioc->ui.fcs.length;
		} else {
			scb->buff_ptr = ioc->data;
			scb->iDataSize = 4096;	// TODO:check it
		}

		set_mbox_xfer_addr (adapter, scb, mboxioc, FROMTO_DEVICE);
		mboxioc->numsgelements = 0;
		break;
	}

	return scb;
}

static int megadev_close (struct inode *inode, struct file *filep)
{
	MOD_DEC_USE_COUNT;
	return 0;
}


static int mega_support_ext_cdb(adapter_t *adapter)
{
	unsigned char raw_mbox[16];
	mbox_t *mbox;
	int ret;

	mbox = (mbox_t *) raw_mbox;

	memset(mbox, 0, sizeof (mbox));
	/*
	 * issue command to find out if controller supports extended CDBs.
	 */
	raw_mbox[0] = 0xA4;
	raw_mbox[2] = 0x16;

	ret = issue_scb_block(adapter, raw_mbox);

	return !ret;
}


/*
 * Find out if this controller supports random deletion and addition of
 * logical drives
 */
static int mega_support_random_del(adapter_t *adapter)
{
	unsigned char raw_mbox[16];
	mbox_t *mbox;
	int ret;

	mbox = (mbox_t *)raw_mbox;

	memset(mbox, 0, sizeof(mbox));

	/*
	 * issue command
	 */
	raw_mbox[0] = FC_DEL_LOGDRV;
	raw_mbox[2] = OP_SUP_DEL_LOGDRV;

	ret = issue_scb_block(adapter, raw_mbox);

	return !ret;
}

static int mega_del_logdrv(adapter_t *adapter, int logdrv)
{
	DECLARE_WAIT_QUEUE_HEAD(wq);
	unsigned long flags;
	scb_t *scb;
	int rval;

	/*
	 * Stop sending commands to the controller, queue them internally.
	 * When deletion is complete, ISR will flush the queue.
	 */
	spin_lock_irqsave(&adapter->lock, flags);
	adapter->quiescent = 1;
	spin_unlock_irqrestore(&adapter->lock, flags);

	while( atomic_read(&adapter->pend_cmds) > 0 ) {
		sleep_on_timeout( &wq, 1*HZ );	/* sleep for 1s */
	}
	rval = mega_do_del_logdrv(adapter, logdrv);

	spin_lock_irqsave(&adapter->lock, flags);

	/*
	 * If delete operation was successful, add 0x80 to the logical drive
	 * ids for commands in the pending queue.
	 */
	if (adapter->read_ldidmap) {
		list_t *pos;
		list_for_each(pos, &adapter->pending_list) {
			scb = list_entry(pos, scb_t, list);
			if (scb->pthru->logdrv < 0x80 )
				scb->pthru->logdrv += 0x80;
		}
	}
	adapter->quiescent = 0;

	mega_runpendq(adapter);

	spin_unlock_irqrestore(&adapter->lock, flags);

	return rval;
}


static int mega_do_del_logdrv(adapter_t *adapter, int logdrv)
{
	unsigned char raw_mbox[16];
	mbox_t *mbox;
	int rval;

	mbox = (mbox_t *)raw_mbox;

	memset(mbox, 0, sizeof(mbox));

	raw_mbox[0] = FC_DEL_LOGDRV;
	raw_mbox[2] = OP_DEL_LOGDRV;
	raw_mbox[3] = logdrv;

	rval = issue_scb_block(adapter, raw_mbox);

	/* log this event */
	if (rval) {
		printk("megaraid: Attempt to delete logical drive %d failed.",
				logdrv);
		return rval;
	}

	/*
	 * After deleting first logical drive, the logical drives must be
	 * addressed by adding 0x80 to the logical drive id.
	 */
	adapter->read_ldidmap = 1;

	return rval;
}

static Scsi_Host_Template driver_template = MEGARAID;

#include "scsi_module.c"

/* vi: set ts=4: */
