diff options
author | Linus Torvalds <torvalds@ppc970.osdl.org> | 2005-04-16 15:20:36 -0700 |
---|---|---|
committer | Linus Torvalds <torvalds@ppc970.osdl.org> | 2005-04-16 15:20:36 -0700 |
commit | 1da177e4c3f41524e886b7f1b8a0c1fc7321cac2 (patch) | |
tree | 0bba044c4ce775e45a88a51686b5d9f90697ea9d /drivers/block/cciss_scsi.c |
Linux-2.6.12-rc2v2.6.12-rc2
Initial git repository build. I'm not bothering with the full history,
even though we have it. We can create a separate "historical" git
archive of that later if we want to, and in the meantime it's about
3.2GB when imported into git - space that would just make the early
git days unnecessarily complicated, when we don't have a lot of good
infrastructure for it.
Let it rip!
Diffstat (limited to 'drivers/block/cciss_scsi.c')
-rw-r--r-- | drivers/block/cciss_scsi.c | 1417 |
1 files changed, 1417 insertions, 0 deletions
diff --git a/drivers/block/cciss_scsi.c b/drivers/block/cciss_scsi.c new file mode 100644 index 00000000000..f16e3caed58 --- /dev/null +++ b/drivers/block/cciss_scsi.c @@ -0,0 +1,1417 @@ +/* + * 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 iss_storagedev@hp.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/scsi_cmnd.h> +#include <scsi/scsi_device.h> +#include <scsi/scsi_host.h> +#include <asm/atomic.h> +#include <linux/timer.h> +#include <linux/completion.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 cmd_type); + + +static int cciss_scsi_proc_info( + struct Scsi_Host *sh, + 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 func); /* 0 == read, 1 == write */ + +static int cciss_scsi_queue_command (struct scsi_cmnd *cmd, + void (* done)(struct scsi_cmnd *)); + +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 }, +}; + +static struct scsi_host_template cciss_driver_template = { + .module = THIS_MODULE, + .name = "cciss", + .proc_name = "cciss", + .proc_info = cciss_scsi_proc_info, + .queuecommand = cciss_scsi_queue_command, + .can_queue = SCSI_CCISS_CAN_QUEUE, + .this_id = 7, + .sg_tablesize = MAXSGENTRIES, + .cmd_per_lun = 1, + .use_clustering = DISABLE_CLUSTERING, +}; + +#pragma pack(1) +struct cciss_scsi_cmd_stack_elem_t { + CommandList_struct cmd; + ErrorInfo_struct Err; + __u32 busaddr; +}; + +#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]; +}; + +#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); + +static CommandList_struct * +scsi_cmd_alloc(ctlr_info_t *h) +{ + /* assume only one process in here at a time, locking done by caller. */ + /* use CCISS_LOCK(ctlr) */ + /* might be better to rewrite how we allocate scsi commands in a way that */ + /* needs no locking at all. */ + + /* 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, 0, sizeof(*c)); */ + 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; + /* (__u32) (stk->cmd_pool_handle + + (sizeof(struct cciss_scsi_cmd_stack_elem_t)*stk->top)); */ + + temp64.val = (__u64) (c->busaddr + sizeof(CommandList_struct)); + /* (__u64) (stk->cmd_pool_handle + + (sizeof(struct cciss_scsi_cmd_stack_elem_t)*stk->top) + + 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. */ + /* use CCISS_LOCK(ctlr) */ + /* 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, struct cciss_scsi_adapter_data_t *sa) +{ + int i; + struct cciss_scsi_cmd_stack_t *stk; + size_t size; + + stk = &sa->cmd_stack; + size = sizeof(struct cciss_scsi_cmd_stack_elem_t) * CMD_STACK_SIZE; + + // pci_alloc_consistent guarantees 32-bit DMA address will + // be used + + 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%p\n", 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); + + target_taken[SELF_SCSI_ID] = 1; + 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_scsi_setup(int cntl_num) +{ + struct cciss_scsi_adapter_data_t * shba; + + ccissscsi[cntl_num].ndevices = 0; + shba = (struct cciss_scsi_adapter_data_t *) + kmalloc(sizeof(*shba), GFP_KERNEL); + if (shba == NULL) + return; + shba->scsi_host = NULL; + spin_lock_init(&shba->lock); + shba->registered = 0; + if (scsi_cmd_stack_setup(cntl_num, shba) != 0) { + kfree(shba); + shba = NULL; + } + hba[cntl_num]->scsi_ctlr = (void *) shba; + return; +} + +static void +complete_scsi_command( CommandList_struct *cp, int timeout, __u32 tag) +{ + struct 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; + } + + cmd = (struct 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, + 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, + 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) + { +#if 0 + printk(KERN_WARNING "cciss: cmd %p " + "has SCSI Status = %x\n", + cp, + ei->ScsiStatus); +#endif + 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); + } + } + // printk("c:%p:c%db%dt%dl%d ", cmd, ctlr->ctlr, cmd->channel, + // cmd->target, cmd->lun); + cmd->scsi_done(cmd); + scsi_cmd_free(ctlr, cp); +} + +static int +cciss_scsi_detect(int ctlr) +{ + struct Scsi_Host *sh; + int error; + + sh = scsi_host_alloc(&cciss_driver_template, sizeof(struct ctlr_info *)); + if (sh == NULL) + goto fail; + 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; + + ((struct cciss_scsi_adapter_data_t *) + hba[ctlr]->scsi_ctlr)->scsi_host = (void *) sh; + sh->hostdata[0] = (unsigned long) hba[ctlr]; + sh->irq = hba[ctlr]->intr; + sh->unique_id = sh->irq; + error = scsi_add_host(sh, &hba[ctlr]->pdev->dev); + if (error) + goto fail_host_put; + scsi_scan_host(sh); + return 1; + + fail_host_put: + scsi_host_put(sh); + fail: + 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 = 0; + 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, DMA_FROM_DEVICE); + + cp->waiting = &wait; + + /* Put the request on the tail of the request queue */ + spin_lock_irqsave(CCISS_LOCK(c->ctlr), flags); + addQ(&c->reqQ, cp); + c->Qdepth++; + start_io(c); + spin_unlock_irqrestore(CCISS_LOCK(c->ctlr), flags); + + wait_for_completion(&wait); + + /* undo the dma mapping */ + cciss_unmap_one(c->pdev, cp, bufsize, DMA_FROM_DEVICE); + 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; + unsigned long flags; + + spin_lock_irqsave(CCISS_LOCK(c->ctlr), flags); + cp = scsi_cmd_alloc(c); + spin_unlock_irqrestore(CCISS_LOCK(c->ctlr), flags); + + if (cp == NULL) { /* trouble... */ + printk("cmd_alloc returned NULL!\n"); + return -1; + } + + ei = cp->err_info; + + 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); + rc = -1; + } + spin_lock_irqsave(CCISS_LOCK(c->ctlr), flags); + scsi_cmd_free(c, cp); + spin_unlock_irqrestore(CCISS_LOCK(c->ctlr), flags); + return rc; +} + +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; + unsigned long flags; + + spin_lock_irqsave(CCISS_LOCK(c->ctlr), flags); + cp = scsi_cmd_alloc(c); + spin_unlock_irqrestore(CCISS_LOCK(c->ctlr), flags); + 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); + rc = -1; + } + spin_lock_irqsave(CCISS_LOCK(c->ctlr), flags); + scsi_cmd_free(c, cp); + spin_unlock_irqrestore(CCISS_LOCK(c->ctlr), flags); + return rc; +} + +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 + will 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"); + goto out; + } + + + /* adjust our table of devices */ + for(i=0; i<num_luns; i++) + { + int devtype; + + /* for each physical lun, do an inquiry */ + if (ld_buff->LUN[i][3] & 0xC0) continue; + 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 */ + if (ncurrent >= CCISS_MAX_SCSI_DEVS_PER_HBA) { + printk(KERN_INFO "cciss%d: %s ignored, " + "too many devices.\n", cntl_num, + DEVICETYPE(devtype)); + break; + } + memcpy(¤tsd[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); +out: + 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; +} + + +static int +cciss_scsi_proc_info(struct Scsi_Host *sh, + 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 func) /* 0 == read, 1 == write */ +{ + + int buflen, datalen; + ctlr_info_t *ci; + int cntl_num; + + + 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", sh->host_no); + + 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, sh->host_no, + buffer, length); +} + +/* cciss_scatter_gather takes a struct 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, + struct 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, + 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, + 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; +} + + +static int +cciss_scsi_queue_command (struct scsi_cmnd *cmd, void (* done)(struct scsi_cmnd *)) +{ + ctlr_info_t **c; + int ctlr, rc; + unsigned char scsi3addr[8]; + CommandList_struct *cp; + unsigned long flags; + + // 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->device->host->hostdata[0]; + ctlr = (*c)->ctlr; + + rc = lookup_scsi3addr(ctlr, cmd->device->channel, cmd->device->id, + cmd->device->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);*/ + // printk("q:%p:c%db%dt%dl%d ", cmd, 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. */ + + spin_lock_irqsave(CCISS_LOCK(ctlr), flags); + cp = scsi_cmd_alloc(*c); + spin_unlock_irqrestore(CCISS_LOCK(ctlr), flags); + 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 = 0; + 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 DMA_TO_DEVICE: cp->Request.Type.Direction = XFER_WRITE; break; + case DMA_FROM_DEVICE: cp->Request.Type.Direction = XFER_READ; break; + case DMA_NONE: cp->Request.Type.Direction = XFER_NONE; break; + case DMA_BIDIRECTIONAL: + // 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 */ + + spin_lock_irqsave(CCISS_LOCK(ctlr), flags); + addQ(&(*c)->reqQ, cp); + (*c)->Qdepth++; + start_io(*c); + spin_unlock_irqrestore(CCISS_LOCK(ctlr), flags); + + /* the cmd'll come back via intr handler in complete_scsi_command() */ + return 0; +} + +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(CCISS_LOCK(ctlr), 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 (sa->registered) { + spin_unlock_irqrestore(CCISS_LOCK(ctlr), flags); + scsi_remove_host(sa->scsi_host); + scsi_host_put(sa->scsi_host); + spin_lock_irqsave(CCISS_LOCK(ctlr), flags); + } + + /* set scsi_host to NULL so our detect routine will + find us on register */ + sa->scsi_host = NULL; + scsi_cmd_stack_free(ctlr); + kfree(sa); + spin_unlock_irqrestore(CCISS_LOCK(ctlr), flags); +} + +static int +cciss_register_scsi(int ctlr) +{ + unsigned long flags; + + CPQ_TAPE_LOCK(ctlr, flags); + + /* Since this is really a block driver, the SCSI core may not be + initialized at init time, in which case, calling scsi_register_host + would hang. Instead, we do it later, via /proc filesystem + and rc scripts, when we know SCSI core is good to go. */ + + /* 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 cciss_scsi_detect(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(CCISS_LOCK(ctlr), 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(CCISS_LOCK(ctlr), flags); + return ENXIO; + } + spin_unlock_irqrestore(CCISS_LOCK(ctlr), flags); + cciss_update_non_disk_devices(ctlr, -1); + cciss_register_scsi(ctlr); + return 0; +} + +static void +cciss_proc_tape_report(int ctlr, unsigned char *buffer, off_t *pos, off_t *len) +{ + unsigned long flags; + int size; + + *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_scsi_setup(cntl_num) +#define cciss_unregister_scsi(ctlr) +#define cciss_register_scsi(ctlr) +#define cciss_proc_tape_report(ctlr, buffer, pos, len) + +#endif /* CONFIG_CISS_SCSI_TAPE */ |