patch-2.4.19 linux-2.4.19/drivers/block/cciss_scsi.c

Next file: linux-2.4.19/drivers/block/cciss_scsi.h
Previous file: linux-2.4.19/drivers/block/cciss_cmd.h
Back to the patch index
Back to the overall index

diff -urN linux-2.4.18/drivers/block/cciss_scsi.c linux-2.4.19/drivers/block/cciss_scsi.c
@@ -0,0 +1,1602 @@
+/*
+ *    Disk Array driver for Compaq SA53xx Controllers, SCSI Tape module
+ *    Copyright 2001 Compaq Computer 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.
+ *
+ *    This program is distributed in the hope that it will be useful,
+ *    but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *    MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE, GOOD TITLE or
+ *    NON INFRINGEMENT.  See the GNU General Public License for more details.
+ *
+ *    You should have received a copy of the GNU General Public License
+ *    along with this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ *
+ *    Questions/Comments/Bugfixes to arrays@compaq.com
+ *
+ *    Author: Stephen M. Cameron
+ */
+#ifdef CONFIG_CISS_SCSI_TAPE
+
+/* Here we have code to present the driver as a scsi driver
+   as it is simultaneously presented as a block driver.  The
+   reason for doing this is to allow access to SCSI tape drives
+   through the array controller.  Note in particular, neither
+   physical nor logical disks are presented through the scsi layer. */
+
+#include "../scsi/scsi.h"
+#include "../scsi/hosts.h"
+#include <asm/atomic.h>
+#include <linux/timer.h>
+
+#include "cciss_scsi.h"
+
+/* some prototypes... */
+static int sendcmd(
+	__u8	cmd,
+	int	ctlr,
+	void	*buff,
+	size_t	size,
+	unsigned int use_unit_num, /* 0: address the controller,
+				      1: address logical volume log_unit,
+				      2: address is in scsi3addr */
+	unsigned int log_unit,
+	__u8	page_code,
+	unsigned char *scsi3addr );
+
+
+int __init cciss_scsi_detect(Scsi_Host_Template *tpnt);
+int cciss_scsi_release(struct Scsi_Host *sh);
+const char *cciss_scsi_info(struct Scsi_Host *sa);
+
+int cciss_scsi_proc_info(
+		char *buffer, /* data buffer */
+		char **start, 	   /* where data in buffer starts */
+		off_t offset,	   /* offset from start of imaginary file */
+		int length, 	   /* length of data in buffer */
+		int hostnum, 	   /* which host adapter (always zero for me) */
+		int func);	   /* 0 == read, 1 == write */
+
+int cciss_scsi_queue_command (Scsi_Cmnd *cmd, void (* done)(Scsi_Cmnd *));
+#if 0
+int cciss_scsi_abort(Scsi_Cmnd *cmd);
+#if defined SCSI_RESET_SYNCHRONOUS && defined SCSI_RESET_ASYNCHRONOUS
+int cciss_scsi_reset(Scsi_Cmnd *cmd, unsigned int reset_flags);
+#else
+int cciss_scsi_reset(Scsi_Cmnd *cmd);
+#endif
+#endif
+
+static struct cciss_scsi_hba_t ccissscsi[MAX_CTLR] = {
+	{ name: "cciss0", ndevices: 0 },
+	{ name: "cciss1", ndevices: 0 },
+	{ name: "cciss2", ndevices: 0 },
+	{ name: "cciss3", ndevices: 0 },
+	{ name: "cciss4", ndevices: 0 },
+	{ name: "cciss5", ndevices: 0 },
+	{ name: "cciss6", ndevices: 0 },
+	{ name: "cciss7", ndevices: 0 },
+};
+
+/* We need one Scsi_Host_Template *per controller* instead of
+   the usual one Scsi_Host_Template per controller *type*. This
+   is so PCI hot plug could have a remote possibility of still
+   working even with the SCSI system.  It's so
+   scsi_unregister_module will differentiate the controllers.
+   When register_scsi_module is called, each host template is
+   customized (name change) in cciss_register_scsi()
+   (that's called from cciss.c:cciss_init_one()) */
+
+static
+Scsi_Host_Template driver_template[MAX_CTLR] =
+{
+	CCISS_SCSI, CCISS_SCSI, CCISS_SCSI, CCISS_SCSI,
+	CCISS_SCSI, CCISS_SCSI, CCISS_SCSI, CCISS_SCSI,
+};
+
+#pragma pack(1)
+struct cciss_scsi_cmd_stack_elem_t {
+	CommandList_struct cmd;
+	ErrorInfo_struct Err;
+	__u32 busaddr; // 32 bits always, must fit through cmd register.
+};
+
+#pragma pack()
+
+#define CMD_STACK_SIZE (SCSI_CCISS_CAN_QUEUE * \
+		CCISS_MAX_SCSI_DEVS_PER_HBA + 2)
+			// plus two for init time usage
+
+#pragma pack(1)
+struct cciss_scsi_cmd_stack_t {
+	struct cciss_scsi_cmd_stack_elem_t *pool;
+	struct cciss_scsi_cmd_stack_elem_t *elem[CMD_STACK_SIZE];
+	dma_addr_t cmd_pool_handle;
+	int top;
+};
+#pragma pack()
+
+struct cciss_scsi_adapter_data_t {
+	struct Scsi_Host *scsi_host;
+	struct cciss_scsi_cmd_stack_t cmd_stack;
+	int registered;
+	spinlock_t lock; // to protect ccissscsi[ctlr];
+};
+#if 1
+#define CPQ_TAPE_LOCK(ctlr, flags) spin_lock_irqsave( \
+	&(((struct cciss_scsi_adapter_data_t *) \
+	hba[ctlr]->scsi_ctlr)->lock), flags);
+#define CPQ_TAPE_UNLOCK(ctlr, flags) spin_unlock_irqrestore( \
+	&(((struct cciss_scsi_adapter_data_t *) \
+	hba[ctlr]->scsi_ctlr)->lock), flags);
+#else
+#define CPQ_TAPE_LOCK(x,y)
+#define CPQ_TAPE_UNLOCK(x,y)
+#endif
+
+static CommandList_struct *
+scsi_cmd_alloc(ctlr_info_t *h)
+{
+	/* assume only one process in here at a time, locking done by caller. */
+
+	/* take the top memory chunk off the stack and return it, if any. */
+	struct cciss_scsi_cmd_stack_elem_t *c;
+	struct cciss_scsi_adapter_data_t *sa;
+	struct cciss_scsi_cmd_stack_t *stk;
+	u64bit temp64;
+
+	sa = (struct cciss_scsi_adapter_data_t *) h->scsi_ctlr;
+	stk = &sa->cmd_stack;
+
+	if (stk->top < 0)
+		return NULL;
+	c = stk->elem[stk->top];
+	memset(&c->cmd, 0, sizeof(c->cmd));
+	memset(&c->Err, 0, sizeof(c->Err));
+	/* set physical addr of cmd and addr of scsi parameters */
+	c->cmd.busaddr = c->busaddr;
+
+	temp64.val = (__u64) (c->busaddr + sizeof(CommandList_struct));
+	stk->top--;
+	c->cmd.ErrDesc.Addr.lower = temp64.val32.lower;
+	c->cmd.ErrDesc.Addr.upper = temp64.val32.upper;
+	c->cmd.ErrDesc.Len = sizeof(ErrorInfo_struct);
+
+	c->cmd.ctlr = h->ctlr;
+	c->cmd.err_info = &c->Err;
+
+	return (CommandList_struct *) c;
+}
+
+static void
+scsi_cmd_free(ctlr_info_t *h, CommandList_struct *cmd)
+{
+	/* assume only one process in here at a time, locking done by caller. */
+	/* drop the free memory chunk on top of the stack. */
+
+	struct cciss_scsi_adapter_data_t *sa;
+	struct cciss_scsi_cmd_stack_t *stk;
+
+	sa = (struct cciss_scsi_adapter_data_t *) h->scsi_ctlr;
+	stk = &sa->cmd_stack;
+	if (stk->top >= CMD_STACK_SIZE) {
+		printk("cciss: scsi_cmd_free called too many times.\n");
+		BUG();
+	}
+	stk->top++;
+	stk->elem[stk->top] = (struct cciss_scsi_cmd_stack_elem_t *) cmd;
+}
+
+static int
+scsi_cmd_stack_setup(int ctlr)
+{
+	int i;
+	struct cciss_scsi_adapter_data_t *sa;
+	struct cciss_scsi_cmd_stack_t *stk;
+	size_t size;
+
+	sa = (struct cciss_scsi_adapter_data_t *) hba[ctlr]->scsi_ctlr;
+	stk = &sa->cmd_stack;
+	size = sizeof(struct cciss_scsi_cmd_stack_elem_t) * CMD_STACK_SIZE;
+
+	stk->pool = (struct cciss_scsi_cmd_stack_elem_t *)
+		pci_alloc_consistent(hba[ctlr]->pdev, size, &stk->cmd_pool_handle);
+
+	if (stk->pool == NULL) {
+		printk("stk->pool is null\n");
+		return -1;
+	}
+
+	for (i=0; i<CMD_STACK_SIZE; i++) {
+		stk->elem[i] = &stk->pool[i];
+		stk->elem[i]->busaddr = (__u32) (stk->cmd_pool_handle +
+			(sizeof(struct cciss_scsi_cmd_stack_elem_t) * i));
+	}
+	stk->top = CMD_STACK_SIZE-1;
+	return 0;
+}
+
+static void
+scsi_cmd_stack_free(int ctlr)
+{
+	struct cciss_scsi_adapter_data_t *sa;
+	struct cciss_scsi_cmd_stack_t *stk;
+	size_t size;
+
+	sa = (struct cciss_scsi_adapter_data_t *) hba[ctlr]->scsi_ctlr;
+	stk = &sa->cmd_stack;
+	if (stk->top != CMD_STACK_SIZE-1) {
+		printk( "cciss: %d scsi commands are still outstanding.\n",
+			CMD_STACK_SIZE - stk->top);
+		// BUG();
+		printk("WE HAVE A BUG HERE!!! stk=0x%08x\n",
+			(unsigned int) stk);
+	}
+	size = sizeof(struct cciss_scsi_cmd_stack_elem_t) * CMD_STACK_SIZE;
+
+	pci_free_consistent(hba[ctlr]->pdev, size, stk->pool, stk->cmd_pool_handle);
+	stk->pool = NULL;
+}
+
+/* scsi_device_types comes from scsi.h */
+#define DEVICETYPE(n) (n<0 || n>MAX_SCSI_DEVICE_CODE) ? \
+	"Unknown" : scsi_device_types[n]
+
+#if 0
+static int xmargin=8;
+static int amargin=60;
+
+static void
+print_bytes (unsigned char *c, int len, int hex, int ascii)
+{
+
+	int i;
+	unsigned char *x;
+
+	if (hex)
+	{
+		x = c;
+		for (i=0;i<len;i++)
+		{
+			if ((i % xmargin) == 0 && i>0) printk("\n");
+			if ((i % xmargin) == 0) printk("0x%04x:", i);
+			printk(" %02x", *x);
+			x++;
+		}
+		printk("\n");
+	}
+	if (ascii)
+	{
+		x = c;
+		for (i=0;i<len;i++)
+		{
+			if ((i % amargin) == 0 && i>0) printk("\n");
+			if ((i % amargin) == 0) printk("0x%04x:", i);
+			if (*x > 26 && *x < 128) printk("%c", *x);
+			else printk(".");
+			x++;
+		}
+		printk("\n");
+	}
+}
+
+static void
+print_cmd(CommandList_struct *cp)
+{
+	printk("queue:%d\n", cp->Header.ReplyQueue);
+	printk("sglist:%d\n", cp->Header.SGList);
+	printk("sgtot:%d\n", cp->Header.SGTotal);
+	printk("Tag:0x%08x/0x%08x\n", cp->Header.Tag.upper,
+			cp->Header.Tag.lower);
+	printk("LUN:0x%02x%02x%02x%02x%02x%02x%02x%02x\n",
+		cp->Header.LUN.LunAddrBytes[0],
+		cp->Header.LUN.LunAddrBytes[1],
+		cp->Header.LUN.LunAddrBytes[2],
+		cp->Header.LUN.LunAddrBytes[3],
+		cp->Header.LUN.LunAddrBytes[4],
+		cp->Header.LUN.LunAddrBytes[5],
+		cp->Header.LUN.LunAddrBytes[6],
+		cp->Header.LUN.LunAddrBytes[7]);
+	printk("CDBLen:%d\n", cp->Request.CDBLen);
+	printk("Type:%d\n",cp->Request.Type.Type);
+	printk("Attr:%d\n",cp->Request.Type.Attribute);
+	printk(" Dir:%d\n",cp->Request.Type.Direction);
+	printk("Timeout:%d\n",cp->Request.Timeout);
+	printk( "CDB: %02x %02x %02x %02x %02x %02x %02x %02x"
+		" %02x %02x %02x %02x %02x %02x %02x %02x\n",
+		cp->Request.CDB[0], cp->Request.CDB[1],
+		cp->Request.CDB[2], cp->Request.CDB[3],
+		cp->Request.CDB[4], cp->Request.CDB[5],
+		cp->Request.CDB[6], cp->Request.CDB[7],
+		cp->Request.CDB[8], cp->Request.CDB[9],
+		cp->Request.CDB[10], cp->Request.CDB[11],
+		cp->Request.CDB[12], cp->Request.CDB[13],
+		cp->Request.CDB[14], cp->Request.CDB[15]),
+	printk("edesc.Addr: 0x%08x/0%08x, Len  = %d\n",
+		cp->ErrDesc.Addr.upper, cp->ErrDesc.Addr.lower,
+			cp->ErrDesc.Len);
+	printk("sgs..........Errorinfo:\n");
+	printk("scsistatus:%d\n", cp->err_info->ScsiStatus);
+	printk("senselen:%d\n", cp->err_info->SenseLen);
+	printk("cmd status:%d\n", cp->err_info->CommandStatus);
+	printk("resid cnt:%d\n", cp->err_info->ResidualCnt);
+	printk("offense size:%d\n", cp->err_info->MoreErrInfo.Invalid_Cmd.offense_size);
+	printk("offense byte:%d\n", cp->err_info->MoreErrInfo.Invalid_Cmd.offense_num);
+	printk("offense value:%d\n", cp->err_info->MoreErrInfo.Invalid_Cmd.offense_value);
+
+}
+
+#endif
+
+static int
+find_bus_target_lun(int ctlr, int *bus, int *target, int *lun)
+{
+	/* finds an unused bus, target, lun for a new device */
+	/* assumes hba[ctlr]->scsi_ctlr->lock is held */
+	int i, found=0;
+	unsigned char target_taken[CCISS_MAX_SCSI_DEVS_PER_HBA];
+
+	memset(&target_taken[0], 0, CCISS_MAX_SCSI_DEVS_PER_HBA);
+
+#	if SELF_SCSI_ID >= 0
+		target_taken[SELF_SCSI_ID] = 1;
+#	endif
+	for (i=0;i<ccissscsi[ctlr].ndevices;i++)
+		target_taken[ccissscsi[ctlr].dev[i].target] = 1;
+
+	for (i=0;i<CCISS_MAX_SCSI_DEVS_PER_HBA;i++) {
+		if (!target_taken[i]) {
+			*bus = 0; *target=i; *lun = 0; found=1;
+			break;
+		}
+	}
+	return (!found);
+}
+
+static int
+cciss_scsi_add_entry(int ctlr, int hostno,
+		unsigned char *scsi3addr, int devtype)
+{
+	/* assumes hba[ctlr]->scsi_ctlr->lock is held */
+	int n = ccissscsi[ctlr].ndevices;
+	struct cciss_scsi_dev_t *sd;
+
+	if (n >= CCISS_MAX_SCSI_DEVS_PER_HBA) {
+		printk("cciss%d: Too many devices, "
+			"some will be inaccessible.\n", ctlr);
+		return -1;
+	}
+	sd = &ccissscsi[ctlr].dev[n];
+	if (find_bus_target_lun(ctlr, &sd->bus, &sd->target, &sd->lun) != 0)
+		return -1;
+	memcpy(&sd->scsi3addr[0], scsi3addr, 8);
+	sd->devtype = devtype;
+	ccissscsi[ctlr].ndevices++;
+
+	/* initially, (before registering with scsi layer) we don't
+	   know our hostno and we don't want to print anything first
+	   time anyway (the scsi layer's inquiries will show that info) */
+	if (hostno != -1)
+		printk("cciss%d: %s device c%db%dt%dl%d added.\n",
+			ctlr, DEVICETYPE(sd->devtype), hostno,
+			sd->bus, sd->target, sd->lun);
+	return 0;
+}
+
+static void
+cciss_scsi_remove_entry(int ctlr, int hostno, int entry)
+{
+	/* assumes hba[ctlr]->scsi_ctlr->lock is held */
+	int i;
+	struct cciss_scsi_dev_t sd;
+
+	if (entry < 0 || entry >= CCISS_MAX_SCSI_DEVS_PER_HBA) return;
+	sd = ccissscsi[ctlr].dev[entry];
+	for (i=entry;i<ccissscsi[ctlr].ndevices-1;i++)
+		ccissscsi[ctlr].dev[i] = ccissscsi[ctlr].dev[i+1];
+	ccissscsi[ctlr].ndevices--;
+	printk("cciss%d: %s device c%db%dt%dl%d removed.\n",
+		ctlr, DEVICETYPE(sd.devtype), hostno,
+			sd.bus, sd.target, sd.lun);
+}
+
+
+#define SCSI3ADDR_EQ(a,b) ( \
+	(a)[7] == (b)[7] && \
+	(a)[6] == (b)[6] && \
+	(a)[5] == (b)[5] && \
+	(a)[4] == (b)[4] && \
+	(a)[3] == (b)[3] && \
+	(a)[2] == (b)[2] && \
+	(a)[1] == (b)[1] && \
+	(a)[0] == (b)[0])
+
+static int
+adjust_cciss_scsi_table(int ctlr, int hostno,
+	struct cciss_scsi_dev_t sd[], int nsds)
+{
+	/* sd contains scsi3 addresses and devtypes, but
+	   bus target and lun are not filled in.  This funciton
+	   takes what's in sd to be the current and adjusts
+	   ccissscsi[] to be in line with what's in sd. */
+
+	int i,j, found, changes=0;
+	struct cciss_scsi_dev_t *csd;
+	unsigned long flags;
+
+	CPQ_TAPE_LOCK(ctlr, flags);
+
+	/* find any devices in ccissscsi[] that are not in
+	   sd[] and remove them from ccissscsi[] */
+
+	i = 0;
+	while(i<ccissscsi[ctlr].ndevices) {
+		csd = &ccissscsi[ctlr].dev[i];
+		found=0;
+		for (j=0;j<nsds;j++) {
+			if (SCSI3ADDR_EQ(sd[j].scsi3addr,
+				csd->scsi3addr)) {
+				if (sd[j].devtype == csd->devtype)
+					found=2;
+				else
+					found=1;
+				break;
+			}
+		}
+
+		if (found == 0) { /* device no longer present. */
+			changes++;
+			/* printk("cciss%d: %s device c%db%dt%dl%d removed.\n",
+				ctlr, DEVICETYPE(csd->devtype), hostno,
+					csd->bus, csd->target, csd->lun); */
+			cciss_scsi_remove_entry(ctlr, hostno, i);
+			/* note, i not incremented */
+		}
+		else if (found == 1) { /* device is different kind */
+			changes++;
+			printk("cciss%d: device c%db%dt%dl%d type changed "
+				"(device type now %s).\n",
+				ctlr, hostno, csd->bus, csd->target, csd->lun,
+					DEVICETYPE(csd->devtype));
+			csd->devtype = sd[j].devtype;
+			i++;	/* so just move along. */
+		} else 		/* device is same as it ever was, */
+			i++;	/* so just move along. */
+	}
+
+	/* Now, make sure every device listed in sd[] is also
+ 	   listed in ccissscsi[], adding them if they aren't found */
+
+	for (i=0;i<nsds;i++) {
+		found=0;
+		for (j=0;j<ccissscsi[ctlr].ndevices;j++) {
+			csd = &ccissscsi[ctlr].dev[j];
+			if (SCSI3ADDR_EQ(sd[i].scsi3addr,
+				csd->scsi3addr)) {
+				if (sd[i].devtype == csd->devtype)
+					found=2;	/* found device */
+				else
+					found=1; 	/* found a bug. */
+				break;
+			}
+		}
+		if (!found) {
+			changes++;
+			if (cciss_scsi_add_entry(ctlr, hostno,
+				&sd[i].scsi3addr[0], sd[i].devtype) != 0)
+				break;
+		} else if (found == 1) {
+			/* should never happen... */
+			changes++;
+			printk("cciss%d: device unexpectedly changed type\n",
+				ctlr);
+			/* but if it does happen, we just ignore that device */
+		}
+	}
+	CPQ_TAPE_UNLOCK(ctlr, flags);
+
+	if (!changes)
+		printk("cciss%d: No device changes detected.\n", ctlr);
+
+	return 0;
+}
+
+static int
+lookup_scsi3addr(int ctlr, int bus, int target, int lun, char *scsi3addr)
+{
+	int i;
+	struct cciss_scsi_dev_t *sd;
+	unsigned long flags;
+
+	CPQ_TAPE_LOCK(ctlr, flags);
+	for (i=0;i<ccissscsi[ctlr].ndevices;i++) {
+		sd = &ccissscsi[ctlr].dev[i];
+		if (sd->bus == bus &&
+		    sd->target == target &&
+		    sd->lun == lun) {
+			memcpy(scsi3addr, &sd->scsi3addr[0], 8);
+			CPQ_TAPE_UNLOCK(ctlr, flags);
+			return 0;
+		}
+	}
+	CPQ_TAPE_UNLOCK(ctlr, flags);
+	return -1;
+}
+
+
+static void
+cciss_find_non_disk_devices(int cntl_num)
+{
+	ReportLunData_struct *ld_buff;
+	InquiryData_struct *inq_buff;
+	int return_code;
+	int i;
+	int listlength = 0;
+	int num_luns;
+	unsigned char scsi3addr[8];
+	unsigned long flags;
+	int reportlunsize = sizeof(*ld_buff) + CISS_MAX_PHYS_LUN * 8;
+
+	hba[cntl_num]->scsi_ctlr = (void *)
+		kmalloc(sizeof(struct cciss_scsi_adapter_data_t),
+			GFP_KERNEL);
+	if (hba[cntl_num]->scsi_ctlr == NULL)
+		return;
+
+	((struct cciss_scsi_adapter_data_t *)
+		hba[cntl_num]->scsi_ctlr)->scsi_host = NULL;
+	((struct cciss_scsi_adapter_data_t *)
+		hba[cntl_num]->scsi_ctlr)->lock = SPIN_LOCK_UNLOCKED;
+	((struct cciss_scsi_adapter_data_t *)
+		hba[cntl_num]->scsi_ctlr)->registered = 0;
+
+	if (scsi_cmd_stack_setup(cntl_num) != 0) {
+		printk("Trouble, returned non-zero!\n");
+		return;
+	}
+
+	ld_buff = kmalloc(reportlunsize, GFP_KERNEL);
+	if (ld_buff == NULL) {
+		printk(KERN_ERR "cciss: out of memory\n");
+		return;
+	}
+	memset(ld_buff, 0, sizeof(ReportLunData_struct));
+	inq_buff = kmalloc(sizeof( InquiryData_struct), GFP_KERNEL);
+	if (inq_buff == NULL) {
+		printk(KERN_ERR "cciss: out of memory\n");
+		kfree(ld_buff);
+		return;
+	}
+
+	/* Get the physical luns */
+	return_code = sendcmd(CISS_REPORT_PHYS, cntl_num, ld_buff,
+			reportlunsize, 0, 0, 0, NULL );
+
+	if( return_code == IO_OK) {
+		unsigned char *c = &ld_buff->LUNListLength[0];
+		listlength = (c[0] << 24) | (c[1] << 16) | (c[2] << 8) | c[3];
+	}
+	else {  /* getting report of physical luns failed */
+		printk(KERN_WARNING "cciss: report physical luns"
+			" command failed\n");
+		listlength = 0;
+	}
+
+	CPQ_TAPE_LOCK(cntl_num, flags);
+	ccissscsi[cntl_num].ndevices = 0;
+	num_luns = listlength / 8; // 8 bytes pre entry
+	/* printk("Found %d LUNs\n", num_luns); */
+
+	if (num_luns > CISS_MAX_PHYS_LUN)
+	{
+		printk(KERN_WARNING
+			"cciss: Maximum physical LUNs (%d) exceeded.  "
+			"%d LUNs ignored.\n", CISS_MAX_PHYS_LUN,
+			num_luns - CISS_MAX_PHYS_LUN);
+		num_luns = CISS_MAX_PHYS_LUN;
+	}
+
+	for(i=0; i<num_luns; i++) {
+		/* Execute an inquiry to figure the device type */
+		memset(inq_buff, 0, sizeof(InquiryData_struct));
+		memcpy(scsi3addr, ld_buff->LUN[i], 8); /* ugly... */
+		return_code = sendcmd(CISS_INQUIRY, cntl_num, inq_buff,
+			sizeof(InquiryData_struct), 2, 0 ,0, scsi3addr );
+	  	if (return_code == IO_OK) {
+			if(inq_buff->data_byte[8] == 0xFF)
+			{
+			   printk(KERN_WARNING "cciss: inquiry failed\n");
+			} else {
+			   int devtype;
+
+			   /* printk("Inquiry...\n");
+			   print_bytes((unsigned char *) inq_buff, 36, 1, 1); */
+			   devtype = (inq_buff->data_byte[0] & 0x1f);
+
+			   switch (devtype)
+			   {
+			    case 0x01: /* sequential access, (tape) */
+			    case 0x08: /* medium changer */
+					  /* this is the only kind of dev */
+					  /* we want to expose here. */
+				if (cciss_scsi_add_entry(cntl_num, -1,
+					(unsigned char *) ld_buff->LUN[i],
+					devtype) != 0)
+						i=num_luns; // leave loop
+				break;
+			    default:
+				break;
+			   }
+
+			}
+		}
+		else printk("cciss: inquiry failed.\n");
+	}
+#if 0
+	for (i=0;i<ccissscsi[cntl_num].ndevices;i++)
+		printk("Tape device presented at c%db%dt%dl%d\n",
+			cntl_num, // <-- this is wrong
+			ccissscsi[cntl_num].dev[i].bus,
+			ccissscsi[cntl_num].dev[i].target,
+			ccissscsi[cntl_num].dev[i].lun);
+#endif
+	CPQ_TAPE_UNLOCK(cntl_num, flags);
+	kfree(ld_buff);
+	kfree(inq_buff);
+	return;
+}
+
+static void
+complete_scsi_command( CommandList_struct *cp, int timeout, __u32 tag)
+{
+	Scsi_Cmnd *cmd;
+	ctlr_info_t *ctlr;
+	u64bit addr64;
+	ErrorInfo_struct *ei;
+
+	ei = cp->err_info;
+
+	/* First, see if it was a message rather than a command */
+	if (cp->Request.Type.Type == TYPE_MSG)  {
+		cp->cmd_type = CMD_MSG_DONE;
+		return;
+	}
+
+	/* we stored ptr to scsi cmd in the buffer head pointer */
+	cmd = (Scsi_Cmnd *) cp->scsi_cmd;
+	ctlr = hba[cp->ctlr];
+
+	/* undo the DMA mappings */
+
+	if (cmd->use_sg) {
+		pci_unmap_sg(ctlr->pdev,
+			cmd->buffer, cmd->use_sg,
+				scsi_to_pci_dma_dir(cmd->sc_data_direction));
+	}
+	else if (cmd->request_bufflen) {
+		addr64.val32.lower = cp->SG[0].Addr.lower;
+		addr64.val32.upper = cp->SG[0].Addr.upper;
+		pci_unmap_single(ctlr->pdev, (dma_addr_t) addr64.val,
+			cmd->request_bufflen,
+				scsi_to_pci_dma_dir(cmd->sc_data_direction));
+	}
+
+	cmd->result = (DID_OK << 16); 		/* host byte */
+	cmd->result |= (COMMAND_COMPLETE << 8);	/* msg byte */
+	/* cmd->result |= (GOOD < 1); */		/* status byte */
+
+	cmd->result |= (ei->ScsiStatus);
+	/* printk("Scsistatus is 0x%02x\n", ei->ScsiStatus);  */
+
+	/* copy the sense data whether we need to or not. */
+
+	memcpy(cmd->sense_buffer, ei->SenseInfo,
+		ei->SenseLen > SCSI_SENSE_BUFFERSIZE ?
+			SCSI_SENSE_BUFFERSIZE :
+			ei->SenseLen);
+	cmd->resid = ei->ResidualCnt;
+
+	if(ei->CommandStatus != 0)
+	{ /* an error has occurred */
+		switch(ei->CommandStatus)
+		{
+			case CMD_TARGET_STATUS:
+				/* Pass it up to the upper layers... */
+				if( ei->ScsiStatus)
+					cmd->result |= (ei->ScsiStatus < 1);
+				else {  /* scsi status is zero??? How??? */
+
+	/* Ordinarily, this case should never happen, but there is a bug
+	   in some released firmware revisions that allows it to happen
+	   if, for example, a 4100 backplane loses power and the tape
+	   drive is in it.  We assume that it's a fatal error of some
+	   kind because we can't show that it wasn't. We will make it
+	   look like selection timeout since that is the most common
+	   reason for this to occur, and it's severe enough. */
+
+					cmd->result = DID_NO_CONNECT << 16;
+				}
+			break;
+			case CMD_DATA_UNDERRUN: /* let mid layer handle it. */
+			break;
+			case CMD_DATA_OVERRUN:
+				printk(KERN_WARNING "cciss: cp %p has"
+					" completed with data overrun "
+					"reported\n", cp);
+			break;
+			case CMD_INVALID: {
+				/* print_bytes(cp, sizeof(*cp), 1, 0);
+				print_cmd(cp); */
+     /* We get CMD_INVALID if you address a non-existent tape drive instead
+	of a selection timeout (no response).  You will see this if you yank
+	out a tape drive, then try to access it. This is kind of a shame
+	because it means that any other CMD_INVALID (e.g. driver bug) will
+	get interpreted as a missing target. */
+				cmd->result = DID_NO_CONNECT << 16;
+				}
+			break;
+			case CMD_PROTOCOL_ERR:
+				printk(KERN_WARNING "cciss: cp %p has "
+					"protocol error \n", cp);
+			break;
+			case CMD_HARDWARE_ERR:
+				cmd->result = DID_ERROR << 16;
+				printk(KERN_WARNING "cciss: cp %p had "
+					" hardware error\n", cp);
+			break;
+			case CMD_CONNECTION_LOST:
+				cmd->result = DID_ERROR << 16;
+				printk(KERN_WARNING "cciss: cp %p had "
+					"connection lost\n", cp);
+			break;
+			case CMD_ABORTED:
+				cmd->result = DID_ABORT << 16;
+				printk(KERN_WARNING "cciss: cp %p was "
+					"aborted\n", cp);
+			break;
+			case CMD_ABORT_FAILED:
+				cmd->result = DID_ERROR << 16;
+				printk(KERN_WARNING "cciss: cp %p reports "
+					"abort failed\n", cp);
+			break;
+			case CMD_UNSOLICITED_ABORT:
+				cmd->result = DID_ABORT << 16;
+				printk(KERN_WARNING "cciss: cp %p aborted "
+					"do to an unsolicited abort\n", cp);
+			break;
+			case CMD_TIMEOUT:
+				cmd->result = DID_TIME_OUT << 16;
+				printk(KERN_WARNING "cciss: cp %p timedout\n",
+					cp);
+			break;
+			default:
+				cmd->result = DID_ERROR << 16;
+				printk(KERN_WARNING "cciss: cp %p returned "
+					"unknown status %x\n", cp,
+						ei->CommandStatus);
+		}
+	}
+	cmd->scsi_done(cmd);
+	scsi_cmd_free(ctlr, cp);
+}
+
+/* cciss_scsi_detect is called from the scsi mid layer.
+   The scsi mid layer (scsi_register_module) is
+   called from cciss.c:cciss_init_one().  */
+
+int __init
+cciss_scsi_detect(Scsi_Host_Template *tpnt)
+{
+	int i;
+	struct Scsi_Host *sh;
+
+	/* Tell the kernel we want to be a SCSI driver... */
+	sh = scsi_register(tpnt, sizeof(struct ctlr_info *));
+	if (sh == NULL) return 0;
+
+	sh->io_port = 0;	// good enough?  FIXME,
+	sh->n_io_port = 0;	// I don't think we use these two...
+
+	sh->this_id = SELF_SCSI_ID;
+
+	/* This is a bit kludgey, using the adapter name to figure out */
+	/* which scsi host template we've got, won't scale beyond 9 ctlrs. */
+	i = tpnt->name[5] - '0';
+
+#	if MAX_CTLR > 9
+#		error "cciss_scsi.c: MAX_CTLR > 9, code maintenance needed."
+#	endif
+
+	if (i<0 || i>=MAX_CTLR || hba[i] == NULL) {
+		/* we didn't find ourself... we shouldn't get here. */
+		printk("cciss_scsi_detect: could not find ourself in hba[]\n");
+		return 0;
+	}
+
+	((struct cciss_scsi_adapter_data_t *)
+		hba[i]->scsi_ctlr)->scsi_host = (void *) sh;
+	sh->hostdata[0] = (unsigned long) hba[i];
+	sh->irq = hba[i]->intr;
+	sh->unique_id = sh->irq;
+	scsi_set_pci_device(sh, hba[i]->pdev);
+
+	return 1;	/* Say we have 1 scsi adapter, this will be */
+			/* called multiple times, once for each adapter */
+			/* from cciss.c:cciss_init_one().  We do it this */
+			/* way for PCI-hot plug reasons. (we don't know how */
+			/* many adapters we have total, so we say we have */
+			/* 1, each of a unique type.) */
+}
+
+static void __exit cleanup_cciss_module(void);
+int
+cciss_scsi_release(struct Scsi_Host *sh)
+{
+	return 0;
+}
+
+static void
+cciss_unmap_one(struct pci_dev *pdev,
+		CommandList_struct *cp,
+		size_t buflen,
+		int data_direction)
+{
+	u64bit addr64;
+
+	addr64.val32.lower = cp->SG[0].Addr.lower;
+	addr64.val32.upper = cp->SG[0].Addr.upper;
+	pci_unmap_single(pdev, (dma_addr_t) addr64.val, buflen, data_direction);
+}
+
+static void
+cciss_map_one(struct pci_dev *pdev,
+		CommandList_struct *cp,
+		unsigned char *buf,
+		size_t buflen,
+		int data_direction)
+{
+	__u64 addr64;
+
+	addr64 = (__u64) pci_map_single(pdev, buf, buflen, data_direction);
+	cp->SG[0].Addr.lower =
+	  (__u32) (addr64 & (__u64) 0x00000000FFFFFFFF);
+	cp->SG[0].Addr.upper =
+	  (__u32) ((addr64 >> 32) & (__u64) 0x00000000FFFFFFFF);
+	cp->SG[0].Len = buflen;
+	cp->Header.SGList = (__u8) 1;   /* no. SGs contig in this cmd */
+	cp->Header.SGTotal = (__u16) 1; /* total sgs in this cmd list */
+}
+
+static int
+cciss_scsi_do_simple_cmd(ctlr_info_t *c,
+			CommandList_struct *cp,
+			unsigned char *scsi3addr,
+			unsigned char *cdb,
+			unsigned char cdblen,
+			unsigned char *buf, int bufsize,
+			int direction)
+{
+	unsigned long flags;
+	DECLARE_COMPLETION(wait);
+
+	cp->cmd_type = CMD_IOCTL_PEND;		// treat this like an ioctl
+	cp->scsi_cmd = NULL;
+	cp->Header.ReplyQueue = 0;  // unused in simple mode
+	memcpy(&cp->Header.LUN, scsi3addr, sizeof(cp->Header.LUN));
+	cp->Header.Tag.lower = cp->busaddr;  // Use k. address of cmd as tag
+	// Fill in the request block...
+
+	/* printk("Using scsi3addr 0x%02x%0x2%0x2%0x2%0x2%0x2%0x2%0x2\n",
+		scsi3addr[0], scsi3addr[1], scsi3addr[2], scsi3addr[3],
+		scsi3addr[4], scsi3addr[5], scsi3addr[6], scsi3addr[7]); */
+
+	memset(cp->Request.CDB, 0, sizeof(cp->Request.CDB));
+	memcpy(cp->Request.CDB, cdb, cdblen);
+	cp->Request.Timeout = 1000;		// guarantee completion.
+	cp->Request.CDBLen = cdblen;
+	cp->Request.Type.Type = TYPE_CMD;
+	cp->Request.Type.Attribute = ATTR_SIMPLE;
+	cp->Request.Type.Direction = direction;
+
+	/* Fill in the SG list and do dma mapping */
+	cciss_map_one(c->pdev, cp,
+			(unsigned char *) buf, bufsize,
+			scsi_to_pci_dma_dir(SCSI_DATA_READ));
+
+	cp->waiting = &wait;
+
+	/* Put the request on the tail of the request queue */
+	spin_lock_irqsave(&io_request_lock, flags);
+	addQ(&c->reqQ, cp);
+	c->Qdepth++;
+	start_io(c);
+	spin_unlock_irqrestore(&io_request_lock, flags);
+
+	wait_for_completion(&wait);
+
+	/* undo the dma mapping */
+	cciss_unmap_one(c->pdev, cp, bufsize,
+				scsi_to_pci_dma_dir(SCSI_DATA_READ));
+
+	return(0);
+}
+
+static void
+cciss_scsi_interpret_error(CommandList_struct *cp)
+{
+	ErrorInfo_struct *ei;
+
+	ei = cp->err_info;
+	switch(ei->CommandStatus)
+	{
+		case CMD_TARGET_STATUS:
+			printk(KERN_WARNING "cciss: cmd %p has "
+				"completed with errors\n", cp);
+			printk(KERN_WARNING "cciss: cmd %p "
+				"has SCSI Status = %x\n",
+					cp,
+					ei->ScsiStatus);
+			if (ei->ScsiStatus == 0)
+				printk(KERN_WARNING
+				"cciss:SCSI status is abnormally zero.  "
+				"(probably indicates selection timeout "
+				"reported incorrectly due to a known "
+				"firmware bug, circa July, 2001.)\n");
+		break;
+		case CMD_DATA_UNDERRUN: /* let mid layer handle it. */
+			printk("UNDERRUN\n");
+		break;
+		case CMD_DATA_OVERRUN:
+			printk(KERN_WARNING "cciss: cp %p has"
+				" completed with data overrun "
+				"reported\n", cp);
+		break;
+		case CMD_INVALID: {
+			/* controller unfortunately reports SCSI passthru's */
+			/* to non-existent targets as invalid commands. */
+			printk(KERN_WARNING "cciss: cp %p is "
+				"reported invalid (probably means "						"target device no longer present)\n",
+				cp);
+			/* print_bytes((unsigned char *) cp, sizeof(*cp), 1, 0);
+			print_cmd(cp);  */
+			}
+		break;
+		case CMD_PROTOCOL_ERR:
+			printk(KERN_WARNING "cciss: cp %p has "
+				"protocol error \n", cp);
+		break;
+		case CMD_HARDWARE_ERR:
+			/* cmd->result = DID_ERROR << 16; */
+			printk(KERN_WARNING "cciss: cp %p had "
+				" hardware error\n", cp);
+		break;
+		case CMD_CONNECTION_LOST:
+			printk(KERN_WARNING "cciss: cp %p had "
+				"connection lost\n", cp);
+		break;
+		case CMD_ABORTED:
+			printk(KERN_WARNING "cciss: cp %p was "
+				"aborted\n", cp);
+		break;
+		case CMD_ABORT_FAILED:
+			printk(KERN_WARNING "cciss: cp %p reports "
+				"abort failed\n", cp);
+		break;
+		case CMD_UNSOLICITED_ABORT:
+			printk(KERN_WARNING "cciss: cp %p aborted "
+				"do to an unsolicited abort\n", cp);
+		break;
+		case CMD_TIMEOUT:
+			printk(KERN_WARNING "cciss: cp %p timedout\n",
+				cp);
+		break;
+		default:
+			printk(KERN_WARNING "cciss: cp %p returned "
+				"unknown status %x\n", cp,
+					ei->CommandStatus);
+	}
+}
+
+static int
+cciss_scsi_do_inquiry(ctlr_info_t *c, unsigned char *scsi3addr,
+		 InquiryData_struct *buf)
+{
+	int rc;
+	CommandList_struct *cp;
+	char cdb[6];
+	ErrorInfo_struct *ei;
+
+	cp = scsi_cmd_alloc(c);
+	ei = cp->err_info;
+
+	if (cp == NULL) {			/* trouble... */
+		printk("cmd_alloc returned NULL!\n");
+		return -1;
+	}
+
+	cdb[0] = CISS_INQUIRY;
+	cdb[1] = 0;
+	cdb[2] = 0;
+	cdb[3] = 0;
+	cdb[4] = sizeof(*buf) & 0xff;
+	cdb[5] = 0;
+	rc = cciss_scsi_do_simple_cmd(c, cp, scsi3addr, cdb,
+				6, (unsigned char *) buf,
+				sizeof(*buf), XFER_READ);
+
+	if (rc != 0) return rc; /* something went wrong */
+
+	if (ei->CommandStatus != 0 &&
+	    ei->CommandStatus != CMD_DATA_UNDERRUN) {
+		cciss_scsi_interpret_error(cp);
+		scsi_cmd_free(c, cp);
+		return -1;
+	}
+	scsi_cmd_free(c, cp);
+	return 0;
+}
+
+static int
+cciss_scsi_do_report_phys_luns(ctlr_info_t *c,
+		ReportLunData_struct *buf, int bufsize)
+{
+	int rc;
+	CommandList_struct *cp;
+	unsigned char cdb[12];
+	unsigned char scsi3addr[8];
+	ErrorInfo_struct *ei;
+
+	cp = scsi_cmd_alloc(c);
+	if (cp == NULL) {			/* trouble... */
+		printk("cmd_alloc returned NULL!\n");
+		return -1;
+	}
+
+	memset(&scsi3addr[0], 0, 8); /* address the controller */
+	cdb[0] = CISS_REPORT_PHYS;
+	cdb[1] = 0;
+	cdb[2] = 0;
+	cdb[3] = 0;
+	cdb[4] = 0;
+	cdb[5] = 0;
+	cdb[6] = (bufsize >> 24) & 0xFF;  //MSB
+	cdb[7] = (bufsize >> 16) & 0xFF;
+	cdb[8] = (bufsize >> 8) & 0xFF;
+	cdb[9] = bufsize & 0xFF;
+	cdb[10] = 0;
+	cdb[11] = 0;
+
+	rc = cciss_scsi_do_simple_cmd(c, cp, scsi3addr,
+				cdb, 12,
+				(unsigned char *) buf,
+				bufsize, XFER_READ);
+
+	if (rc != 0) return rc; /* something went wrong */
+
+	ei = cp->err_info;
+	if (ei->CommandStatus != 0 &&
+	    ei->CommandStatus != CMD_DATA_UNDERRUN) {
+		cciss_scsi_interpret_error(cp);
+		scsi_cmd_free(c, cp);
+		return -1;
+	}
+	scsi_cmd_free(c, cp);
+	return 0;
+}
+
+static void
+cciss_update_non_disk_devices(int cntl_num, int hostno)
+{
+	/* the idea here is we could get notified from /proc
+	   that some devices have changed, so we do a report
+	   physical luns cmd, and adjust our list of devices
+	   accordingly.  (We can't rely on the scsi-mid layer just
+	   doing inquiries, because the "busses" that the scsi
+	   mid-layer probes are totally fabricated by this driver,
+	   so new devices wouldn't show up.
+
+	   the scsi3addr's of devices won't change so long as the
+	   adapter is not reset.  That means we can rescan and
+	   tell which devices we already know about, vs. new
+	   devices, vs.  disappearing devices.
+
+	   Also, if you yank out a tape drive, then put in a disk
+	   in it's place, (say, a configured volume from another
+	   array controller for instance)  _don't_ poke this driver
+	   (so it thinks it's still a tape, but _do_ poke the scsi
+	   mid layer, so it does an inquiry... the scsi mid layer
+	   could see the physical disk.  This would be bad.  Need to
+	   think about how to prevent that.  One idea would be to
+	   snoop all scsi responses and if an inquiry repsonse comes
+	   back that reports a disk, chuck it an return selection
+	   timeout instead and adjust our table...  Not sure i like
+	   that though.
+
+	 */
+
+	ReportLunData_struct *ld_buff;
+	InquiryData_struct *inq_buff;
+	unsigned char scsi3addr[8];
+	ctlr_info_t *c;
+	__u32 num_luns=0;
+	unsigned char *ch;
+	/* unsigned char found[CCISS_MAX_SCSI_DEVS_PER_HBA]; */
+	struct cciss_scsi_dev_t currentsd[CCISS_MAX_SCSI_DEVS_PER_HBA];
+	int ncurrent=0;
+	int reportlunsize = sizeof(*ld_buff) + CISS_MAX_PHYS_LUN * 8;
+	int i;
+
+	c = (ctlr_info_t *) hba[cntl_num];
+	ld_buff = kmalloc(reportlunsize, GFP_KERNEL);
+	if (ld_buff == NULL) {
+		printk(KERN_ERR "cciss: out of memory\n");
+		return;
+	}
+	memset(ld_buff, 0, reportlunsize);
+	inq_buff = kmalloc(sizeof( InquiryData_struct), GFP_KERNEL);
+	if (inq_buff == NULL) {
+		printk(KERN_ERR "cciss: out of memory\n");
+		kfree(ld_buff);
+		return;
+	}
+
+	if (cciss_scsi_do_report_phys_luns(c, ld_buff, reportlunsize) == 0) {
+		ch = &ld_buff->LUNListLength[0];
+		num_luns = ((ch[0]<<24) | (ch[1]<<16) | (ch[2]<<8) | ch[3]) / 8;
+		if (num_luns > CISS_MAX_PHYS_LUN) {
+			printk(KERN_WARNING
+				"cciss: Maximum physical LUNs (%d) exceeded.  "
+				"%d LUNs ignored.\n", CISS_MAX_PHYS_LUN,
+				num_luns - CISS_MAX_PHYS_LUN);
+			num_luns = CISS_MAX_PHYS_LUN;
+		}
+	}
+	else {
+		printk(KERN_ERR  "cciss: Report physical LUNs failed.\n");
+		return;
+	}
+
+
+	/* adjust our table of devices */
+	for(i=0; i<num_luns; i++)
+	{
+		int devtype;
+
+		/* for each physical lun, do an inquiry */
+		memset(inq_buff, 0, sizeof(InquiryData_struct));
+		memcpy(&scsi3addr[0], &ld_buff->LUN[i][0], 8);
+
+		if (cciss_scsi_do_inquiry(hba[cntl_num],
+			scsi3addr, inq_buff) != 0)
+		{
+			/* Inquiry failed (msg printed already) */
+			devtype = 0; /* so we will skip this device. */
+		} else /* what kind of device is this? */
+			devtype = (inq_buff->data_byte[0] & 0x1f);
+
+		switch (devtype)
+		{
+		  case 0x01: /* sequential access, (tape) */
+		  case 0x08: /* medium changer */
+			memcpy(&currentsd[ncurrent].scsi3addr[0],
+				&scsi3addr[0], 8);
+			currentsd[ncurrent].devtype = devtype;
+			currentsd[ncurrent].bus = -1;
+			currentsd[ncurrent].target = -1;
+			currentsd[ncurrent].lun = -1;
+			ncurrent++;
+			break;
+		  default:
+			break;
+		}
+	}
+
+	adjust_cciss_scsi_table(cntl_num, hostno, currentsd, ncurrent);
+
+	kfree(inq_buff);
+	kfree(ld_buff);
+	return;
+}
+
+static int
+is_keyword(char *ptr, int len, char *verb)  // Thanks to ncr53c8xx.c
+{
+	int verb_len = strlen(verb);
+	if (len >= verb_len && !memcmp(verb,ptr,verb_len))
+		return verb_len;
+	else
+		return 0;
+}
+
+static int
+cciss_scsi_user_command(int ctlr, int hostno, char *buffer, int length)
+{
+	int arg_len;
+
+	if ((arg_len = is_keyword(buffer, length, "rescan")) != 0)
+		cciss_update_non_disk_devices(ctlr, hostno);
+	else
+		return -EINVAL;
+	return length;
+}
+
+/* It's a pity that we need this, but, we do... */
+extern struct Scsi_Host *scsi_hostlist;  /* from ../scsi/hosts.c */
+
+int
+cciss_scsi_proc_info(char *buffer, /* data buffer */
+		char **start, 	   /* where data in buffer starts */
+		off_t offset,	   /* offset from start of imaginary file */
+		int length, 	   /* length of data in buffer */
+		int hostnum, 	   /* which host adapter (always zero for me) */
+		int func)	   /* 0 == read, 1 == write */
+{
+
+	int buflen, datalen;
+	struct Scsi_Host *sh;
+	int found;
+	ctlr_info_t *ci;
+	int cntl_num;
+
+	/* Lets see if we can find our Scsi_Host...
+	   this might be kind of "bad", searching scis_hostlist this way
+	   but how else can we find the scsi host?  I think I've seen
+	   this coded both ways, (circular list and null terminated list)
+	   I coded it to work either way, since I wasn't sure.  */
+
+	sh = scsi_hostlist;
+	found=0;
+	do {
+		if (sh == NULL) break;
+		if (sh->host_no == hostnum) {
+			found++;
+			break;
+		}
+		sh = sh->next;
+	} while (sh != scsi_hostlist && sh != NULL);
+
+	if (sh == NULL || found == 0) /* This really shouldn't ever happen. */
+		return -EINVAL;
+
+	ci = (ctlr_info_t *) sh->hostdata[0];
+	if (ci == NULL)  /* This really shouldn't ever happen. */
+		return -EINVAL;
+
+	cntl_num = ci->ctlr;	/* Get our index into the hba[] array */
+
+	if (func == 0) {	/* User is reading from /proc/scsi/ciss*?/?*  */
+		buflen = sprintf(buffer, "hostnum=%d\n", hostnum);
+
+		datalen = buflen - offset;
+		if (datalen < 0) { 	/* they're reading past EOF. */
+			datalen = 0;
+			*start = buffer+buflen;
+		} else
+			*start = buffer + offset;
+		return(datalen);
+	} else 	/* User is writing to /proc/scsi/cciss*?/?*  ... */
+		return cciss_scsi_user_command(cntl_num, hostnum,
+			buffer, length);
+}
+
+/* this is via the generic proc support */
+const char *
+cciss_scsi_info(struct Scsi_Host *sa)
+{
+	static char buf[300];
+	ctlr_info_t *ci;
+
+	/* probably need to work on putting a bit more info in here... */
+	/* this is output via the /proc filesystem. */
+
+	ci = (ctlr_info_t *) sa->hostdata[0];
+
+	sprintf(buf, "%s %c%c%c%c\n",
+		ci->product_name,
+		ci->firm_ver[0],
+		ci->firm_ver[1],
+		ci->firm_ver[2],
+		ci->firm_ver[3]);
+
+	return buf;
+}
+
+
+/* cciss_scatter_gather takes a Scsi_Cmnd, (cmd), and does the pci
+   dma mapping  and fills in the scatter gather entries of the
+   cciss command, cp. */
+
+static void
+cciss_scatter_gather(struct pci_dev *pdev,
+		CommandList_struct *cp,
+		Scsi_Cmnd *cmd)
+{
+	unsigned int use_sg, nsegs=0, len;
+	struct scatterlist *scatter = (struct scatterlist *) cmd->buffer;
+	__u64 addr64;
+
+	/* is it just one virtual address? */
+	if (!cmd->use_sg) {
+		if (cmd->request_bufflen) {	/* anything to xfer? */
+
+			addr64 = (__u64) pci_map_single(pdev,
+				cmd->request_buffer,
+				cmd->request_bufflen,
+				scsi_to_pci_dma_dir(cmd->sc_data_direction));
+
+			cp->SG[0].Addr.lower =
+			  (__u32) (addr64 & (__u64) 0x00000000FFFFFFFF);
+			cp->SG[0].Addr.upper =
+			  (__u32) ((addr64 >> 32) & (__u64) 0x00000000FFFFFFFF);
+			cp->SG[0].Len = cmd->request_bufflen;
+			nsegs=1;
+		}
+	} /* else, must be a list of virtual addresses.... */
+	else if (cmd->use_sg <= MAXSGENTRIES) {	/* not too many addrs? */
+
+		use_sg = pci_map_sg(pdev, cmd->buffer, cmd->use_sg,
+			scsi_to_pci_dma_dir(cmd->sc_data_direction));
+
+		for (nsegs=0; nsegs < use_sg; nsegs++) {
+			addr64 = (__u64) sg_dma_address(&scatter[nsegs]);
+			len  = sg_dma_len(&scatter[nsegs]);
+			cp->SG[nsegs].Addr.lower =
+			  (__u32) (addr64 & (__u64) 0x00000000FFFFFFFF);
+			cp->SG[nsegs].Addr.upper =
+			  (__u32) ((addr64 >> 32) & (__u64) 0x00000000FFFFFFFF);
+			cp->SG[nsegs].Len = len;
+			cp->SG[nsegs].Ext = 0;  // we are not chaining
+		}
+	} else BUG();
+
+	cp->Header.SGList = (__u8) nsegs;   /* no. SGs contig in this cmd */
+	cp->Header.SGTotal = (__u16) nsegs; /* total sgs in this cmd list */
+	return;
+}
+
+
+int
+cciss_scsi_queue_command (Scsi_Cmnd *cmd, void (* done)(Scsi_Cmnd *))
+{
+	ctlr_info_t **c;
+	int ctlr, rc;
+	unsigned char scsi3addr[8];
+	CommandList_struct *cp;
+
+	// Get the ptr to our adapter structure (hba[i]) out of cmd->host.
+	// We violate cmd->host privacy here.  (Is there another way?)
+	c = (ctlr_info_t **) &cmd->host->hostdata[0];
+	ctlr = (*c)->ctlr;
+
+	rc = lookup_scsi3addr(ctlr, cmd->channel, cmd->target, cmd->lun,
+			scsi3addr);
+	if (rc != 0) {
+		/* the scsi nexus does not match any that we presented... */
+		/* pretend to mid layer that we got selection timeout */
+		cmd->result = DID_NO_CONNECT << 16;
+		done(cmd);
+		/* we might want to think about registering controller itself
+		   as a processor device on the bus so sg binds to it. */
+		return 0;
+	}
+
+	// printk("cciss_queue_command, p=%p, cmd=0x%02x, c%db%dt%dl%d\n",
+	//	cmd, cmd->cmnd[0], ctlr, cmd->channel, cmd->target, cmd->lun);
+
+	/* Ok, we have a reasonable scsi nexus, so send the cmd down, and
+		see what the device thinks of it. */
+
+	cp = scsi_cmd_alloc(*c);
+	if (cp == NULL) {			/* trouble... */
+		printk("scsi_cmd_alloc returned NULL!\n");
+		/* FIXME: next 3 lines are -> BAD! <- */
+		cmd->result = DID_NO_CONNECT << 16;
+		done(cmd);
+		return 0;
+	}
+
+	// Fill in the command list header
+
+	cmd->scsi_done = done;    // save this for use by completion code
+
+	// save cp in case we have to abort it
+	cmd->host_scribble = (unsigned char *) cp;
+
+	cp->cmd_type = CMD_SCSI;
+	cp->scsi_cmd = cmd;
+	cp->Header.ReplyQueue = 0;  // unused in simple mode
+	memcpy(&cp->Header.LUN.LunAddrBytes[0], &scsi3addr[0], 8);
+	cp->Header.Tag.lower = cp->busaddr;  // Use k. address of cmd as tag
+
+	// Fill in the request block...
+
+	cp->Request.Timeout = 1000; // guarantee completion
+	memset(cp->Request.CDB, 0, sizeof(cp->Request.CDB));
+	if (cmd->cmd_len > sizeof(cp->Request.CDB)) BUG();
+	cp->Request.CDBLen = cmd->cmd_len;
+	memcpy(cp->Request.CDB, cmd->cmnd, cmd->cmd_len);
+	cp->Request.Type.Type = TYPE_CMD;
+	cp->Request.Type.Attribute = ATTR_SIMPLE;
+	switch(cmd->sc_data_direction)
+	{
+	  case SCSI_DATA_WRITE: cp->Request.Type.Direction = XFER_WRITE; break;
+	  case SCSI_DATA_READ: cp->Request.Type.Direction = XFER_READ; break;
+	  case SCSI_DATA_NONE: cp->Request.Type.Direction = XFER_NONE; break;
+
+	  case SCSI_DATA_UNKNOWN:
+		// This can happen if a buggy application does a scsi passthru
+		// and sets both inlen and outlen to non-zero. ( see
+		// ../scsi/scsi_ioctl.c:scsi_ioctl_send_command() )
+
+		cp->Request.Type.Direction = XFER_RSVD;
+		// This is technically wrong, and cciss controllers should
+		// reject it with CMD_INVALID, which is the most correct
+		// response, but non-fibre backends appear to let it
+		// slide by, and give the same results as if this field
+		// were set correctly.  Either way is acceptable for
+		// our purposes here.
+
+		break;
+
+	  default:
+		printk("cciss: unknown data direction: %d\n",
+			cmd->sc_data_direction);
+		BUG();
+		break;
+	}
+
+	cciss_scatter_gather((*c)->pdev, cp, cmd); // Fill the SG list
+
+	/* Put the request on the tail of the request queue */
+
+	addQ(&(*c)->reqQ, cp);
+	(*c)->Qdepth++;
+	start_io(*c);
+
+	/* the cmd'll come back via intr handler in complete_scsi_command()  */
+	return 0;
+}
+
+static void
+init_driver_template(int ctlr)
+{
+	memset(&driver_template[ctlr], 0, sizeof(driver_template[ctlr]));
+	driver_template[ctlr].name = ccissscsi[ctlr].name;
+	driver_template[ctlr].proc_name = ccissscsi[ctlr].name;
+	driver_template[ctlr].detect = cciss_scsi_detect;
+	driver_template[ctlr].release = cciss_scsi_release;
+	driver_template[ctlr].proc_info = cciss_scsi_proc_info;
+	driver_template[ctlr].queuecommand = cciss_scsi_queue_command;
+	driver_template[ctlr].eh_abort_handler = NULL;
+	driver_template[ctlr].eh_device_reset_handler = NULL;
+	driver_template[ctlr].bios_param = scsicam_bios_param;
+	driver_template[ctlr].can_queue = SCSI_CCISS_CAN_QUEUE;
+	driver_template[ctlr].this_id = SELF_SCSI_ID;
+	driver_template[ctlr].sg_tablesize = MAXSGENTRIES;
+	driver_template[ctlr].cmd_per_lun = 1;
+	driver_template[ctlr].use_new_eh_code = 1;
+	driver_template[ctlr].use_clustering = DISABLE_CLUSTERING;
+	driver_template[ctlr].module = THIS_MODULE;
+
+	/* set scsi_host to NULL so our detect routine will
+	   find us on register */
+
+	((struct cciss_scsi_adapter_data_t *)
+		hba[ctlr]->scsi_ctlr)->scsi_host = NULL;
+
+}
+
+static void
+cciss_unregister_scsi(int ctlr)
+{
+	struct cciss_scsi_adapter_data_t *sa;
+	struct cciss_scsi_cmd_stack_t *stk;
+	unsigned long flags;
+
+	/* we are being forcibly unloaded, and may not refuse. */
+
+	spin_lock_irqsave(&io_request_lock, flags);
+	sa = (struct cciss_scsi_adapter_data_t *) hba[ctlr]->scsi_ctlr;
+	stk = &sa->cmd_stack;
+
+	/* if we weren't ever actually registered, don't unregister */
+	if (((struct cciss_scsi_adapter_data_t *)
+		hba[ctlr]->scsi_ctlr)->registered) {
+		spin_unlock_irqrestore(&io_request_lock, flags);
+		scsi_unregister_module(MODULE_SCSI_HA, &driver_template[ctlr]);
+		spin_lock_irqsave(&io_request_lock, flags);
+	}
+	init_driver_template(ctlr);
+	scsi_cmd_stack_free(ctlr);
+	kfree(hba[ctlr]->scsi_ctlr);
+	spin_unlock_irqrestore(&io_request_lock, flags);
+}
+
+static int
+cciss_register_scsi(int ctlr, int this_is_init_time)
+{
+	unsigned long flags;
+
+	CPQ_TAPE_LOCK(ctlr, flags);
+	driver_template[ctlr].name = ccissscsi[ctlr].name;
+	driver_template[ctlr].proc_name = ccissscsi[ctlr].name;
+	driver_template[ctlr].module = THIS_MODULE;;
+
+	/* Since this is really a block driver, the SCSI core may not be
+	   initialized yet, in which case, calling scsi_register_module
+	   would hang.  instead, we will do it later, via /proc filesystem
+	   and rc scripts, when we know SCSI core is good to go. */
+
+	if (this_is_init_time) {
+		CPQ_TAPE_UNLOCK(ctlr, flags);
+		return 0;
+	}
+
+	/* Only register if SCSI devices are detected. */
+	if (ccissscsi[ctlr].ndevices != 0) {
+		((struct cciss_scsi_adapter_data_t *)
+			hba[ctlr]->scsi_ctlr)->registered = 1;
+		CPQ_TAPE_UNLOCK(ctlr, flags);
+		return scsi_register_module(MODULE_SCSI_HA,
+			&driver_template[ctlr]);
+	}
+	CPQ_TAPE_UNLOCK(ctlr, flags);
+	printk(KERN_INFO
+		"cciss%d: No appropriate SCSI device detected, "
+		"SCSI subsystem not engaged.\n", ctlr);
+	return 0;
+}
+
+static int
+cciss_engage_scsi(int ctlr)
+{
+	struct cciss_scsi_adapter_data_t *sa;
+	struct cciss_scsi_cmd_stack_t *stk;
+	unsigned long flags;
+
+	spin_lock_irqsave(&io_request_lock, flags);
+	sa = (struct cciss_scsi_adapter_data_t *) hba[ctlr]->scsi_ctlr;
+	stk = &sa->cmd_stack;
+
+	if (((struct cciss_scsi_adapter_data_t *)
+		hba[ctlr]->scsi_ctlr)->registered) {
+		printk("cciss%d: SCSI subsystem already engaged.\n", ctlr);
+		spin_unlock_irqrestore(&io_request_lock, flags);
+		return ENXIO;
+	}
+	spin_unlock_irqrestore(&io_request_lock, flags);
+	cciss_update_non_disk_devices(ctlr, -1);
+	cciss_register_scsi(ctlr, 0);
+	return 0;
+}
+
+static void
+cciss_proc_tape_report(int ctlr, unsigned char *buffer, off_t *pos, off_t *len)
+{
+	int size;
+	unsigned int flags;
+
+	*pos = *pos -1; *len = *len - 1; // cut off the last trailing newline
+
+	CPQ_TAPE_LOCK(ctlr, flags);
+	size = sprintf(buffer + *len,
+		"       Sequential access devices: %d\n\n",
+			ccissscsi[ctlr].ndevices);
+	CPQ_TAPE_UNLOCK(ctlr, flags);
+	*pos += size; *len += size;
+}
+
+#else /* no CONFIG_CISS_SCSI_TAPE */
+
+/* If no tape support, then these become defined out of existence */
+
+#define cciss_find_non_disk_devices(cntl_num)
+#define cciss_unregister_scsi(ctlr)
+#define cciss_register_scsi(ctlr, this_is_init_time)
+#define cciss_proc_tape_report(ctlr, buffer, pos, len)
+
+#endif /* CONFIG_CISS_SCSI_TAPE */

FUNET's LINUX-ADM group, linux-adm@nic.funet.fi
TCL-scripts by Sam Shen (who was at: slshen@lbl.gov)