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/scsi/megaraid.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/scsi/megaraid.c')
-rw-r--r-- | drivers/scsi/megaraid.c | 5122 |
1 files changed, 5122 insertions, 0 deletions
diff --git a/drivers/scsi/megaraid.c b/drivers/scsi/megaraid.c new file mode 100644 index 00000000000..8d707b29027 --- /dev/null +++ b/drivers/scsi/megaraid.c @@ -0,0 +1,5122 @@ +/* + * + * Linux MegaRAID device driver + * + * Copyright © 2002 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. + * + * Copyright (c) 2003 Christoph Hellwig <hch@lst.de> + * - new-style, hotplug-aware pci probing and scsi registration + * + * Version : v2.00.3 (Feb 19, 2003) - Atul Mukker <Atul.Mukker@lsil.com> + * + * Description: Linux device driver for LSI Logic MegaRAID controller + * + * Supported controllers: MegaRAID 418, 428, 438, 466, 762, 467, 471, 490, 493 + * 518, 520, 531, 532 + * + * This driver is supported by LSI Logic, with assistance from Red Hat, Dell, + * and others. Please send updates to the mailing list + * linux-scsi@vger.kernel.org . + * + */ + +#include <linux/mm.h> +#include <linux/fs.h> +#include <linux/blkdev.h> +#include <asm/uaccess.h> +#include <asm/io.h> +#include <linux/delay.h> +#include <linux/proc_fs.h> +#include <linux/reboot.h> +#include <linux/module.h> +#include <linux/list.h> +#include <linux/interrupt.h> +#include <linux/pci.h> +#include <linux/init.h> +#include <scsi/scsicam.h> + +#include "scsi.h" +#include <scsi/scsi_host.h> + +#include "megaraid.h" + +#define MEGARAID_MODULE_VERSION "2.00.3" + +MODULE_AUTHOR ("LSI Logic Corporation"); +MODULE_DESCRIPTION ("LSI Logic MegaRAID driver"); +MODULE_LICENSE ("GPL"); +MODULE_VERSION(MEGARAID_MODULE_VERSION); + +static unsigned int max_cmd_per_lun = DEF_CMD_PER_LUN; +module_param(max_cmd_per_lun, uint, 0); +MODULE_PARM_DESC(max_cmd_per_lun, "Maximum number of commands which can be issued to a single LUN (default=DEF_CMD_PER_LUN=63)"); + +static unsigned short int max_sectors_per_io = MAX_SECTORS_PER_IO; +module_param(max_sectors_per_io, ushort, 0); +MODULE_PARM_DESC(max_sectors_per_io, "Maximum number of sectors per I/O request (default=MAX_SECTORS_PER_IO=128)"); + + +static unsigned short int max_mbox_busy_wait = MBOX_BUSY_WAIT; +module_param(max_mbox_busy_wait, ushort, 0); +MODULE_PARM_DESC(max_mbox_busy_wait, "Maximum wait for mailbox in microseconds if busy (default=MBOX_BUSY_WAIT=10)"); + +#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) + +/* + * Global variables + */ + +static int hba_count; +static adapter_t *hba_soft_state[MAX_CONTROLLERS]; +static struct proc_dir_entry *mega_proc_dir_entry; + +/* For controller re-ordering */ +static struct mega_hbas mega_hbas[MAX_CONTROLLERS]; + +/* + * The File Operations structure for the serial/ioctl interface of the driver + */ +static struct file_operations megadev_fops = { + .owner = THIS_MODULE, + .ioctl = megadev_ioctl, + .open = megadev_open, +}; + +/* + * 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 = 0x02000000; + +/* major number used by the device for character interface */ +static int major; + +#define IS_RAID_CH(hba, ch) (((hba)->mega_ch_class >> (ch)) & 0x01) + + +/* + * Debug variable to print some diagnostic messages + */ +static int trace_level; + +/** + * mega_setup_mailbox() + * @adapter - pointer to our soft state + * + * Allocates a 8 byte aligned memory for the handshake mailbox. + */ +static int +mega_setup_mailbox(adapter_t *adapter) +{ + unsigned long align; + + adapter->una_mbox64 = pci_alloc_consistent(adapter->dev, + sizeof(mbox64_t), &adapter->una_mbox64_dma); + + if( !adapter->una_mbox64 ) return -1; + + adapter->mbox = &adapter->una_mbox64->mbox; + + adapter->mbox = (mbox_t *)((((unsigned long) adapter->mbox) + 15) & + (~0UL ^ 0xFUL)); + + adapter->mbox64 = (mbox64_t *)(((unsigned long)adapter->mbox) - 8); + + align = ((void *)adapter->mbox) - ((void *)&adapter->una_mbox64->mbox); + + adapter->mbox_dma = adapter->una_mbox64_dma + 8 + align; + + /* + * Register the mailbox if the controller is an io-mapped controller + */ + if( adapter->flag & BOARD_IOMAP ) { + + outb_p(adapter->mbox_dma & 0xFF, + adapter->host->io_port + MBOX_PORT0); + + outb_p((adapter->mbox_dma >> 8) & 0xFF, + adapter->host->io_port + MBOX_PORT1); + + outb_p((adapter->mbox_dma >> 16) & 0xFF, + adapter->host->io_port + MBOX_PORT2); + + outb_p((adapter->mbox_dma >> 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_query_adapter() + * @adapter - pointer to our soft state + * + * Issue the adapter inquiry commands to the controller and find out + * information and parameter about the devices attached + */ +static int +mega_query_adapter(adapter_t *adapter) +{ + dma_addr_t prod_info_dma_handle; + mega_inquiry3 *inquiry3; + u8 raw_mbox[sizeof(struct mbox_out)]; + mbox_t *mbox; + int retval; + + /* Initialize adapter inquiry mailbox */ + + mbox = (mbox_t *)raw_mbox; + + memset((void *)adapter->mega_buffer, 0, MEGA_BUFFER_SIZE); + memset(&mbox->m_out, 0, sizeof(raw_mbox)); + + /* + * Try to issue Inquiry3 command + * if not succeeded, then issue MEGA_MBOXCMD_ADAPTERINQ command and + * update enquiry3 structure + */ + mbox->m_out.xferaddr = (u32)adapter->buf_dma_handle; + + inquiry3 = (mega_inquiry3 *)adapter->mega_buffer; + + 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 */ + + mraid_ext_inquiry *ext_inq; + mraid_inquiry *inq; + dma_addr_t dma_handle; + + ext_inq = pci_alloc_consistent(adapter->dev, + sizeof(mraid_ext_inquiry), &dma_handle); + + if( ext_inq == NULL ) return -1; + + inq = &ext_inq->raid_inq; + + mbox->m_out.xferaddr = (u32)dma_handle; + + /*issue old 0x04 command to adapter */ + mbox->m_out.cmd = MEGA_MBOXCMD_ADPEXTINQ; + + issue_scb_block(adapter, raw_mbox); + + /* + * update Enquiry3 and ProductInfo structures with + * mraid_inquiry structure + */ + mega_8_to_40ld(inq, inquiry3, + (mega_product_info *)&adapter->product_info); + + pci_free_consistent(adapter->dev, sizeof(mraid_ext_inquiry), + ext_inq, dma_handle); + + } else { /*adapter supports 40ld */ + adapter->flag |= BOARD_40LD; + + /* + * get product_info, which is static information and will be + * unchanged + */ + prod_info_dma_handle = pci_map_single(adapter->dev, (void *) + &adapter->product_info, + sizeof(mega_product_info), PCI_DMA_FROMDEVICE); + + mbox->m_out.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(KERN_WARNING + "megaraid: Product_info cmd failed with error: %d\n", + retval); + + pci_unmap_single(adapter->dev, prod_info_dma_handle, + sizeof(mega_product_info), PCI_DMA_FROMDEVICE); + } + + + /* + * kernel scans the channels from 0 to <= max_channel + */ + adapter->host->max_channel = + adapter->product_info.nchannels + 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 = inquiry3->num_ldrv; + + adapter->max_cmds = adapter->product_info.max_commands; + + if(adapter->max_cmds > MAX_COMMANDS) + adapter->max_cmds = MAX_COMMANDS; + + adapter->host->can_queue = adapter->max_cmds - 1; + + /* + * Get the maximum number of scatter-gather elements supported by this + * firmware + */ + mega_get_max_sgl(adapter); + + adapter->host->sg_tablesize = adapter->sglen; + + + /* use HP firmware and bios version encoding */ + if (adapter->product_info.subsysvid == HP_SUBSYS_VID) { + sprintf (adapter->fw_version, "%c%d%d.%d%d", + adapter->product_info.fw_version[2], + adapter->product_info.fw_version[1] >> 8, + adapter->product_info.fw_version[1] & 0x0f, + adapter->product_info.fw_version[0] >> 8, + adapter->product_info.fw_version[0] & 0x0f); + sprintf (adapter->bios_version, "%c%d%d.%d%d", + adapter->product_info.bios_version[2], + adapter->product_info.bios_version[1] >> 8, + adapter->product_info.bios_version[1] & 0x0f, + adapter->product_info.bios_version[0] >> 8, + adapter->product_info.bios_version[0] & 0x0f); + } else { + memcpy(adapter->fw_version, + (char *)adapter->product_info.fw_version, 4); + adapter->fw_version[4] = 0; + + memcpy(adapter->bios_version, + (char *)adapter->product_info.bios_version, 4); + + adapter->bios_version[4] = 0; + } + + printk(KERN_NOTICE "megaraid: [%s:%s] detected %d logical drives.\n", + adapter->fw_version, adapter->bios_version, adapter->numldrv); + + /* + * Do we support extended (>10 bytes) cdbs + */ + adapter->support_ext_cdb = mega_support_ext_cdb(adapter); + if (adapter->support_ext_cdb) + printk(KERN_NOTICE "megaraid: supports extended CDBs.\n"); + + + return 0; +} + +/** + * mega_runpendq() + * @adapter - pointer to our soft state + * + * Runs through the list of pending requests. + */ +static inline void +mega_runpendq(adapter_t *adapter) +{ + if(!list_empty(&adapter->pending_list)) + __mega_runpendq(adapter); +} + +/* + * megaraid_queue() + * @scmd - Issue this scsi command + * @done - the callback hook into the scsi mid-layer + * + * The command queuing entry point for the mid-layer. + */ +static int +megaraid_queue(Scsi_Cmnd *scmd, void (*done)(Scsi_Cmnd *)) +{ + adapter_t *adapter; + scb_t *scb; + int busy=0; + + adapter = (adapter_t *)scmd->device->host->hostdata; + + scmd->scsi_done = done; + + + /* + * Allocate and build a SCB request + * busy flag will be set if mega_build_cmd() command could not + * allocate scb. We will return non-zero status in that case. + * NOTE: scb can be null even though certain commands completed + * successfully, e.g., MODE_SENSE and TEST_UNIT_READY, we would + * return 0 in that case. + */ + + scb = mega_build_cmd(adapter, scmd, &busy); + + if(scb) { + scb->state |= SCB_PENDQ; + 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, don't run + * the pending_list. + */ + if(atomic_read(&adapter->quiescent) == 0) { + mega_runpendq(adapter); + } + return 0; + } + + return busy; +} + +/** + * mega_allocate_scb() + * @adapter - pointer to our soft state + * @cmd - scsi command from the mid-layer + * + * Allocate a SCB structure. This is the central structure for controller + * commands. + */ +static inline scb_t * +mega_allocate_scb(adapter_t *adapter, Scsi_Cmnd *cmd) +{ + struct list_head *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 = MEGA_DMA_TYPE_NONE; + + return scb; + } + + return NULL; +} + +/** + * mega_get_ldrv_num() + * @adapter - pointer to our soft state + * @cmd - scsi mid layer command + * @channel - channel on the controller + * + * Calculate the logical drive number based on the information in scsi command + * and the channel number. + */ +static inline int +mega_get_ldrv_num(adapter_t *adapter, Scsi_Cmnd *cmd, int channel) +{ + int tgt; + int ldrv_num; + + tgt = cmd->device->id; + + if ( tgt > adapter->this_id ) + tgt--; /* we do not get inquires for initiator id */ + + 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; +} + +/** + * mega_build_cmd() + * @adapter - pointer to our soft state + * @cmd - Prepare using this scsi command + * @busy - busy flag if no resources + * + * Prepares a command and scatter gather list for the controller. This routine + * also finds out if the commands is intended for a logical drive or a + * physical device and prepares the controller command accordingly. + * + * We also re-order the logical drives and physical devices based on their + * boot settings. + */ +static scb_t * +mega_build_cmd(adapter_t *adapter, Scsi_Cmnd *cmd, int *busy) +{ + mega_ext_passthru *epthru; + mega_passthru *pthru; + scb_t *scb; + mbox_t *mbox; + long seg; + char islogical; + int max_ldrv_num; + int channel = 0; + int target = 0; + int ldrv_num = 0; /* logical drive number */ + + + /* + * filter the internal and ioctl commands + */ + if((cmd->cmnd[0] == MEGA_INTERNAL_CMD)) { + return cmd->buffer; + } + + + /* + * We know what channels our logical drives are on - mega_find_card() + */ + islogical = adapter->logdrv_chan[cmd->device->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->device->channel - + adapter->product_info.nchannels; + } + else { + /* this is physical channel */ + channel = cmd->device->channel; + target = cmd->device->id; + + /* + * 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 ) { + /* this is the logical channel */ + channel = cmd->device->channel; + } + else { + /* physical channel */ + channel = cmd->device->channel - NVIRT_CHAN; + target = cmd->device->id; + } + } + + + if(islogical) { + + /* have just LUN 0 for each target on virtual channels */ + if (cmd->device->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) ? + MAX_LOGICAL_DRIVES_40LD : MAX_LOGICAL_DRIVES_8LD; + + /* + * max_ldrv_num increases by 0x80 if some logical drive was + * deleted. + */ + if(adapter->read_ldidmap) + max_ldrv_num += 0x80; + + if(ldrv_num > max_ldrv_num ) { + cmd->result = (DID_BAD_TARGET << 16); + cmd->scsi_done(cmd); + return NULL; + } + + } + else { + if( cmd->device->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); + +#if MEGA_HAVE_CLUSTERING + /* + * Do we support clustering and is the support enabled + * If no, return success always + */ + if( !adapter->has_cluster ) { + cmd->result = (DID_OK << 16); + cmd->scsi_done(cmd); + return NULL; + } + + if(!(scb = mega_allocate_scb(adapter, cmd))) { + *busy = 1; + return NULL; + } + + scb->raw_mbox[0] = MEGA_CLUSTER_CMD; + scb->raw_mbox[2] = MEGA_RESERVATION_STATUS; + scb->raw_mbox[3] = ldrv_num; + + scb->dma_direction = PCI_DMA_NONE; + + return scb; +#else + cmd->result = (DID_OK << 16); + cmd->scsi_done(cmd); + return NULL; +#endif + + 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->device->channel))) { + + printk(KERN_NOTICE + "scsi%d: scanning scsi channel %d ", + adapter->host->host_no, + cmd->device->channel); + printk("for logical drives.\n"); + + adapter->flag |= (1L << cmd->device->channel); + } + + /* Allocate a SCB and initialize passthru */ + if(!(scb = mega_allocate_scb(adapter, cmd))) { + *busy = 1; + 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; + memcpy(pthru->cdb, cmd->cmnd, cmd->cmd_len); + + if( adapter->has_64bit_addr ) { + mbox->m_out.cmd = MEGA_MBOXCMD_PASSTHRU64; + } + else { + mbox->m_out.cmd = MEGA_MBOXCMD_PASSTHRU; + } + + scb->dma_direction = PCI_DMA_FROMDEVICE; + + pthru->numsgelements = mega_build_sglist(adapter, scb, + &pthru->dataxferaddr, &pthru->dataxferlen); + + mbox->m_out.xferaddr = scb->pthru_dma_addr; + + return scb; + + case READ_6: + case WRITE_6: + case READ_10: + case WRITE_10: + case READ_12: + case WRITE_12: + + /* Allocate a SCB and initialize mailbox */ + if(!(scb = mega_allocate_scb(adapter, cmd))) { + *busy = 1; + return NULL; + } + mbox = (mbox_t *)scb->raw_mbox; + + memset(mbox, 0, sizeof(scb->raw_mbox)); + mbox->m_out.logdrv = ldrv_num; + + /* + * A little hack: 2nd bit is zero for all scsi read + * commands and is set for all scsi write commands + */ + if( adapter->has_64bit_addr ) { + mbox->m_out.cmd = (*cmd->cmnd & 0x02) ? + MEGA_MBOXCMD_LWRITE64: + MEGA_MBOXCMD_LREAD64 ; + } + else { + mbox->m_out.cmd = (*cmd->cmnd & 0x02) ? + MEGA_MBOXCMD_LWRITE: + MEGA_MBOXCMD_LREAD ; + } + + /* + * 6-byte READ(0x08) or WRITE(0x0A) cdb + */ + if( cmd->cmd_len == 6 ) { + mbox->m_out.numsectors = (u32) cmd->cmnd[4]; + mbox->m_out.lba = + ((u32)cmd->cmnd[1] << 16) | + ((u32)cmd->cmnd[2] << 8) | + (u32)cmd->cmnd[3]; + + mbox->m_out.lba &= 0x1FFFFF; + +#if MEGA_HAVE_STATS + /* + * Take modulo 0x80, since the logical drive + * number increases by 0x80 when a logical + * drive was deleted + */ + if (*cmd->cmnd == READ_6) { + adapter->nreads[ldrv_num%0x80]++; + adapter->nreadblocks[ldrv_num%0x80] += + mbox->m_out.numsectors; + } else { + adapter->nwrites[ldrv_num%0x80]++; + adapter->nwriteblocks[ldrv_num%0x80] += + mbox->m_out.numsectors; + } +#endif + } + + /* + * 10-byte READ(0x28) or WRITE(0x2A) cdb + */ + if( cmd->cmd_len == 10 ) { + mbox->m_out.numsectors = + (u32)cmd->cmnd[8] | + ((u32)cmd->cmnd[7] << 8); + mbox->m_out.lba = + ((u32)cmd->cmnd[2] << 24) | + ((u32)cmd->cmnd[3] << 16) | + ((u32)cmd->cmnd[4] << 8) | + (u32)cmd->cmnd[5]; + +#if MEGA_HAVE_STATS + if (*cmd->cmnd == READ_10) { + adapter->nreads[ldrv_num%0x80]++; + adapter->nreadblocks[ldrv_num%0x80] += + mbox->m_out.numsectors; + } else { + adapter->nwrites[ldrv_num%0x80]++; + adapter->nwriteblocks[ldrv_num%0x80] += + mbox->m_out.numsectors; + } +#endif + } + + /* + * 12-byte READ(0xA8) or WRITE(0xAA) cdb + */ + if( cmd->cmd_len == 12 ) { + mbox->m_out.lba = + ((u32)cmd->cmnd[2] << 24) | + ((u32)cmd->cmnd[3] << 16) | + ((u32)cmd->cmnd[4] << 8) | + (u32)cmd->cmnd[5]; + + mbox->m_out.numsectors = + ((u32)cmd->cmnd[6] << 24) | + ((u32)cmd->cmnd[7] << 16) | + ((u32)cmd->cmnd[8] << 8) | + (u32)cmd->cmnd[9]; + +#if MEGA_HAVE_STATS + if (*cmd->cmnd == READ_12) { + adapter->nreads[ldrv_num%0x80]++; + adapter->nreadblocks[ldrv_num%0x80] += + mbox->m_out.numsectors; + } else { + adapter->nwrites[ldrv_num%0x80]++; + adapter->nwriteblocks[ldrv_num%0x80] += + mbox->m_out.numsectors; + } +#endif + } + + /* + * If it is a read command + */ + if( (*cmd->cmnd & 0x0F) == 0x08 ) { + scb->dma_direction = PCI_DMA_FROMDEVICE; + } + else { + scb->dma_direction = PCI_DMA_TODEVICE; + } + + /* Calculate Scatter-Gather info */ + mbox->m_out.numsgelements = mega_build_sglist(adapter, scb, + (u32 *)&mbox->m_out.xferaddr, (u32 *)&seg); + + return scb; + +#if MEGA_HAVE_CLUSTERING + case RESERVE: /* Fall through */ + case RELEASE: + + /* + * Do we support clustering and is the support enabled + */ + if( ! adapter->has_cluster ) { + + cmd->result = (DID_BAD_TARGET << 16); + cmd->scsi_done(cmd); + return NULL; + } + + /* Allocate a SCB and initialize mailbox */ + if(!(scb = mega_allocate_scb(adapter, cmd))) { + *busy = 1; + return NULL; + } + + scb->raw_mbox[0] = MEGA_CLUSTER_CMD; + scb->raw_mbox[2] = ( *cmd->cmnd == RESERVE ) ? + MEGA_RESERVE_LD : MEGA_RELEASE_LD; + + scb->raw_mbox[3] = ldrv_num; + + scb->dma_direction = PCI_DMA_NONE; + + return scb; +#endif + + 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_allocate_scb(adapter, cmd))) { + *busy = 1; + return NULL; + } + + mbox = (mbox_t *)scb->raw_mbox; + memset(mbox, 0, sizeof(scb->raw_mbox)); + + if( adapter->support_ext_cdb ) { + + epthru = mega_prepare_extpassthru(adapter, scb, cmd, + channel, target); + + mbox->m_out.cmd = MEGA_MBOXCMD_EXTPTHRU; + + mbox->m_out.xferaddr = scb->epthru_dma_addr; + + } + else { + + pthru = mega_prepare_passthru(adapter, scb, cmd, + channel, target); + + /* Initialize mailbox */ + if( adapter->has_64bit_addr ) { + mbox->m_out.cmd = MEGA_MBOXCMD_PASSTHRU64; + } + else { + mbox->m_out.cmd = MEGA_MBOXCMD_PASSTHRU; + } + + mbox->m_out.xferaddr = scb->pthru_dma_addr; + + } + return scb; + } + return NULL; +} + + +/** + * mega_prepare_passthru() + * @adapter - pointer to our soft state + * @scb - our scsi control block + * @cmd - scsi command from the mid-layer + * @channel - actual channel on the controller + * @target - actual id on the controller. + * + * prepare a command for the scsi physical devices. + */ +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)); + + /* 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->device->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->device->channel))) { + + printk(KERN_NOTICE + "scsi%d: scanning scsi channel %d [P%d] ", + adapter->host->host_no, + cmd->device->channel, channel); + printk("for physical devices.\n"); + + adapter->flag |= (1L << cmd->device->channel); + } + /* Fall through */ + default: + pthru->numsgelements = mega_build_sglist(adapter, scb, + &pthru->dataxferaddr, &pthru->dataxferlen); + break; + } + return pthru; +} + + +/** + * mega_prepare_extpassthru() + * @adapter - pointer to our soft state + * @scb - our scsi control block + * @cmd - scsi command from the mid-layer + * @channel - actual channel on the controller + * @target - actual id on the controller. + * + * prepare a command for the scsi physical devices. This rountine prepares + * commands for devices which can take extended CDBs (>10 bytes) + */ +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)); + + /* 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->device->lun; + + memcpy(epthru->cdb, cmd->cmnd, cmd->cmd_len); + + /* Not sure about the direction */ + scb->dma_direction = PCI_DMA_BIDIRECTIONAL; + + switch(cmd->cmnd[0]) { + case INQUIRY: + case READ_CAPACITY: + if(!(adapter->flag & (1L << cmd->device->channel))) { + + printk(KERN_NOTICE + "scsi%d: scanning scsi channel %d [P%d] ", + adapter->host->host_no, + cmd->device->channel, channel); + printk("for physical devices.\n"); + + adapter->flag |= (1L << cmd->device->channel); + } + /* Fall through */ + default: + epthru->numsgelements = mega_build_sglist(adapter, scb, + &epthru->dataxferaddr, &epthru->dataxferlen); + break; + } + + return epthru; +} + +static void +__mega_runpendq(adapter_t *adapter) +{ + scb_t *scb; + struct list_head *pos, *next; + + /* 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( issue_scb(adapter, scb) != 0 ) + return; + } + } + + return; +} + + +/** + * issue_scb() + * @adapter - pointer to our soft state + * @scb - scsi control block + * + * Post a command to the card if the mailbox is available, otherwise return + * busy. We also take the scb from the pending list if the mailbox is + * available. + */ +static int +issue_scb(adapter_t *adapter, scb_t *scb) +{ + volatile mbox64_t *mbox64 = adapter->mbox64; + volatile mbox_t *mbox = adapter->mbox; + unsigned int i = 0; + + if(unlikely(mbox->m_in.busy)) { + do { + udelay(1); + i++; + } while( mbox->m_in.busy && (i < max_mbox_busy_wait) ); + + if(mbox->m_in.busy) return -1; + } + + /* Copy mailbox data into host structure */ + memcpy((char *)&mbox->m_out, (char *)scb->raw_mbox, + sizeof(struct mbox_out)); + + mbox->m_out.cmdid = scb->idx; /* Set cmdid */ + mbox->m_in.busy = 1; /* Set busy */ + + + /* + * Increment the pending queue counter + */ + atomic_inc(&adapter->pend_cmds); + + switch (mbox->m_out.cmd) { + case MEGA_MBOXCMD_LREAD64: + case MEGA_MBOXCMD_LWRITE64: + case MEGA_MBOXCMD_PASSTHRU64: + case MEGA_MBOXCMD_EXTPTHRU: + mbox64->xfer_segment_lo = mbox->m_out.xferaddr; + mbox64->xfer_segment_hi = 0; + mbox->m_out.xferaddr = 0xFFFFFFFF; + break; + default: + mbox64->xfer_segment_lo = 0; + mbox64->xfer_segment_hi = 0; + } + + /* + * post the command + */ + scb->state |= SCB_ISSUED; + + if( likely(adapter->flag & BOARD_MEMMAP) ) { + mbox->m_in.poll = 0; + mbox->m_in.ack = 0; + WRINDOOR(adapter, adapter->mbox_dma | 0x1); + } + else { + irq_enable(adapter); + issue_command(adapter); + } + + return 0; +} + +/* + * Wait until the controller's mailbox is available + */ +static inline int +mega_busywait_mbox (adapter_t *adapter) +{ + if (adapter->mbox->m_in.busy) + return __mega_busywait_mbox(adapter); + return 0; +} + +/** + * issue_scb_block() + * @adapter - pointer to our soft state + * @raw_mbox - the mailbox + * + * Issue a scb in synchronous and non-interrupt mode + */ +static int +issue_scb_block(adapter_t *adapter, u_char *raw_mbox) +{ + volatile mbox64_t *mbox64 = adapter->mbox64; + volatile mbox_t *mbox = adapter->mbox; + u8 byte; + + /* Wait until mailbox is free */ + if(mega_busywait_mbox (adapter)) + goto bug_blocked_mailbox; + + /* Copy mailbox data into host structure */ + memcpy((char *) mbox, raw_mbox, sizeof(struct mbox_out)); + mbox->m_out.cmdid = 0xFE; + mbox->m_in.busy = 1; + + switch (raw_mbox[0]) { + case MEGA_MBOXCMD_LREAD64: + case MEGA_MBOXCMD_LWRITE64: + case MEGA_MBOXCMD_PASSTHRU64: + case MEGA_MBOXCMD_EXTPTHRU: + mbox64->xfer_segment_lo = mbox->m_out.xferaddr; + mbox64->xfer_segment_hi = 0; + mbox->m_out.xferaddr = 0xFFFFFFFF; + break; + default: + mbox64->xfer_segment_lo = 0; + mbox64->xfer_segment_hi = 0; + } + + if( likely(adapter->flag & BOARD_MEMMAP) ) { + mbox->m_in.poll = 0; + mbox->m_in.ack = 0; + mbox->m_in.numstatus = 0xFF; + mbox->m_in.status = 0xFF; + WRINDOOR(adapter, adapter->mbox_dma | 0x1); + + while((volatile u8)mbox->m_in.numstatus == 0xFF) + cpu_relax(); + + mbox->m_in.numstatus = 0xFF; + + while( (volatile u8)mbox->m_in.poll != 0x77 ) + cpu_relax(); + + mbox->m_in.poll = 0; |