* [PATCH/RFC 1/2] 3ware 9000 SATA-RAID driver
@ 2004-05-22 18:19 Adam Radford
2004-05-23 10:59 ` hch
0 siblings, 1 reply; 4+ messages in thread
From: Adam Radford @ 2004-05-22 18:19 UTC (permalink / raw)
To: linux-scsi
This email is a resend. My previous patch never appeared on linux-scsi for some
reason, possibly because it was a 143k attachment.
Andrew/James,
These 2 patches contain a new LLD SCSI driver to add support for the 3ware 9000 series
SATA-RAID controllers. The driver is 100% GPL'd.
Please review for inclusion in 2.6.6-mm5.
Anybody who wishes to review the driver implementation and give feedback would be
very appreciated!
--
Adam Radford
Staff Software Engineer
AMCC
diff -Naur linux-2.6.6-mm4/drivers/pci/pci.ids linux-2.6.6-mm5/drivers/pci/pci.ids
--- linux-2.6.6-mm4/drivers/pci/pci.ids 2004-05-21 16:35:20.000000000 -0700
+++ linux-2.6.6-mm5/drivers/pci/pci.ids 2004-05-21 15:38:58.000000000 -0700
@@ -5751,7 +5751,7 @@
13c1 3ware Inc
1000 3ware ATA-RAID
1001 3ware 7000-series ATA-RAID
- 1002 3ware ATA-RAID
+ 1002 3ware 9000-series ATA-RAID
13c2 Technotrend Systemtechnik GmbH
13c3 Janz Computer AG
13c4 Phase Metrics
diff -Naur linux-2.6.6-mm4/drivers/scsi/3w-9xxx.c linux-2.6.6-mm5/drivers/scsi/3w-9xxx.c
--- linux-2.6.6-mm4/drivers/scsi/3w-9xxx.c 1969-12-31 16:00:00.000000000 -0800
+++ linux-2.6.6-mm5/drivers/scsi/3w-9xxx.c 2004-05-21 15:14:48.000000000 -0700
@@ -0,0 +1,2129 @@
+/*
+ 3w-9xxx.c -- 3ware 9000 Storage Controller device driver for Linux.
+
+ Written By: Adam Radford <linuxraid@amcc.com>
+
+ Copyright (C) 2004 Applied Micro Circuits 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; version 2 of the License.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ NO WARRANTY
+ THE PROGRAM IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OR
+ CONDITIONS OF ANY KIND, EITHER EXPRESS OR IMPLIED INCLUDING, WITHOUT
+ LIMITATION, ANY WARRANTIES OR CONDITIONS OF TITLE, NON-INFRINGEMENT,
+ MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Each Recipient is
+ solely responsible for determining the appropriateness of using and
+ distributing the Program and assumes all risks associated with its
+ exercise of rights under this Agreement, including but not limited to
+ the risks and costs of program errors, damage to or loss of data,
+ programs or equipment, and unavailability or interruption of operations.
+
+ DISCLAIMER OF LIABILITY
+ NEITHER RECIPIENT NOR ANY CONTRIBUTORS SHALL HAVE ANY LIABILITY FOR ANY
+ DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ DAMAGES (INCLUDING WITHOUT LIMITATION LOST PROFITS), HOWEVER CAUSED AND
+ ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR
+ TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE
+ USE OR DISTRIBUTION OF THE PROGRAM OR THE EXERCISE OF ANY RIGHTS GRANTED
+ HEREUNDER, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGES
+
+ 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+
+ Bugs/Comments/Suggestions should be mailed to:
+ linuxraid@amcc.com
+
+ For more information, goto:
+ http://www.amcc.com
+
+ Note: This version of the driver does not contain a bundled firmware
+ image.
+
+ History
+ -------
+ 2.26.00.005 - Initial release.
+ 2.26.00.006 - Remove TW_WRITEL macro, replace with writeq().
+ 2.26.00.007 - Skip lun and channel probes.
+ 2.26.00.008 - Fix bug in attention/command interrupt handling.
+*/
+
+#include <linux/module.h>
+
+MODULE_AUTHOR ("AMCC");
+#ifdef CONFIG_SMP
+MODULE_DESCRIPTION ("3ware 9000 Storage Controller Linux Driver (SMP)");
+#else
+MODULE_DESCRIPTION ("3ware 9000 Storage Controller Linux Driver");
+#endif
+MODULE_LICENSE("GPL");
+
+#include <linux/kernel.h>
+#include <linux/pci.h>
+#include <linux/time.h>
+#include <linux/proc_fs.h>
+#include <linux/sched.h>
+#include <linux/ioport.h>
+#include <linux/blkdev.h>
+#include <linux/hdreg.h>
+#include <linux/string.h>
+#include <linux/delay.h>
+#include <linux/smp.h>
+#include <linux/reboot.h>
+#include <linux/spinlock.h>
+#include <linux/interrupt.h>
+#include <linux/moduleparam.h>
+
+#include <asm/errno.h>
+#include <asm/io.h>
+#include <asm/irq.h>
+#include <asm/uaccess.h>
+
+#include "scsi.h"
+#include "hosts.h"
+
+#include "3w-9xxx.h"
+
+/* Notifier block to get a notify on system shutdown/halt/reboot */
+static struct notifier_block twa_notifier = {
+ .notifier_call = twa_halt,
+ .next = NULL,
+ .priority = 0
+};
+
+/* File operations struct for character device */
+static struct file_operations twa_fops = {
+ .owner = THIS_MODULE,
+ .ioctl = twa_chrdev_ioctl,
+ .open = twa_chrdev_open,
+ .release = twa_chrdev_release
+};
+
+/* Globals */
+char *twa_driver_version="2.26.00.008";
+TW_Device_Extension *twa_device_extension_list[TW_MAX_SLOT];
+unsigned int twa_device_extension_count = 0;
+static int twa_major = -1;
+extern struct timezone sys_tz;
+static int cmds_per_lun;
+
+/* Module parameters */
+module_param(cmds_per_lun, int, 0);
+MODULE_PARM_DESC(cmds_per_lun, "Maximum commands per LUN");
+
+/* Functions */
+
+/* This function will complete an aen request from the isr */
+static int twa_aen_complete(TW_Device_Extension *tw_dev, int request_id)
+{
+ TW_Command_Full *full_command_packet;
+ TW_Command *command_packet;
+ TW_Command_Apache_Header *header;
+ unsigned short aen;
+ int retval = 1;
+
+ header = (TW_Command_Apache_Header *)tw_dev->generic_buffer_virt[request_id];
+ tw_dev->posted_request_count--;
+ aen = header->status_block.error;
+ full_command_packet = (TW_Command_Full *)tw_dev->command_packet_virt[request_id];
+ command_packet = &full_command_packet->command.oldcommand;
+
+ /* First check for internal completion of set param for time sync */
+ if (command_packet->byte0_offset.opcode == TW_OP_SET_PARAM) {
+ /* Keep reading the queue in case there are more aen's */
+ if (twa_aen_read_queue(tw_dev, request_id))
+ goto out2;
+ else {
+ retval = 0;
+ goto out;
+ }
+ }
+
+ switch (aen) {
+ case TW_AEN_QUEUE_EMPTY:
+ /* Quit reading the queue if this is the last one */
+ break;
+ case TW_AEN_SYNC_TIME_WITH_HOST:
+ twa_aen_sync_time(tw_dev, request_id);
+ retval = 0;
+ goto out;
+ default:
+ twa_aen_queue_event(tw_dev, header);
+
+ /* If there are more aen's, keep reading the queue */
+ if (twa_aen_read_queue(tw_dev, request_id))
+ goto out2;
+ else {
+ retval = 0;
+ goto out;
+ }
+ }
+ retval = 0;
+out2:
+ tw_dev->state[request_id] = TW_S_COMPLETED;
+ twa_free_request_id(tw_dev, request_id);
+ clear_bit(TW_IN_ATTENTION_LOOP, &tw_dev->flags);
+out:
+ return retval;
+} /* End twa_aen_complete() */
+
+/* This function will drain aen queue */
+static int twa_aen_drain_queue(TW_Device_Extension *tw_dev, int no_check_reset)
+{
+ int request_id = 0;
+ char cdb[TW_MAX_CDB_LEN];
+ TW_SG_Apache sglist[1];
+ int finished = 0, count = 0;
+ TW_Command_Full *full_command_packet;
+ TW_Command_Apache_Header *header;
+ unsigned short aen;
+ int first_reset = 0, queue = 0, retval = 1;
+
+ if (no_check_reset)
+ first_reset = 0;
+ else
+ first_reset = 1;
+
+ full_command_packet = (TW_Command_Full *)tw_dev->command_packet_virt[request_id];
+ memset(full_command_packet, 0, sizeof(TW_Command_Full));
+
+ /* Initialize cdb */
+ memset(&cdb, 0, TW_MAX_CDB_LEN);
+ cdb[0] = REQUEST_SENSE; /* opcode */
+ cdb[4] = TW_ALLOCATION_LENGTH; /* allocation length */
+
+ /* Initialize sglist */
+ memset(&sglist, 0, sizeof(TW_SG_Apache));
+ sglist[0].length = TW_SECTOR_SIZE;
+ sglist[0].address = tw_dev->generic_buffer_phys[request_id];
+
+ if (sglist[0].address & TW_ALIGNMENT_9000_SGL) {
+ TW_PRINTK(tw_dev->host, TW_DRIVER, 0x1, "Found unaligned address during AEN drain");
+ goto out;
+ }
+
+ /* Mark internal command */
+ tw_dev->srb[request_id] = NULL;
+
+ do {
+ /* Send command to the board */
+ if (twa_scsiop_execute_scsi(tw_dev, request_id, cdb, 1, sglist)) {
+ TW_PRINTK(tw_dev->host, TW_DRIVER, 0x2, "Error posting request sense");
+ goto out;
+ }
+
+ /* Now poll for completion */
+ if (twa_poll_response(tw_dev, request_id, 30)) {
+ TW_PRINTK(tw_dev->host, TW_DRIVER, 0x3, "No valid response while draining AEN queue");
+ tw_dev->posted_request_count--;
+ goto out;
+ }
+
+ tw_dev->posted_request_count--;
+ header = (TW_Command_Apache_Header *)tw_dev->generic_buffer_virt[request_id];
+ aen = header->status_block.error;
+ queue = 0;
+ count++;
+
+ switch (aen) {
+ case TW_AEN_QUEUE_EMPTY:
+ if (first_reset != 1)
+ goto out;
+ else
+ finished = 1;
+ break;
+ case TW_AEN_SOFT_RESET:
+ if (first_reset == 0)
+ first_reset = 1;
+ else
+ queue = 1;
+ break;
+ case TW_AEN_SYNC_TIME_WITH_HOST:
+ break;
+ default:
+ queue = 1;
+ }
+
+ /* Now queue an event info */
+ if (queue)
+ twa_aen_queue_event(tw_dev, header);
+ } while ((finished == 0) && (count < TW_MAX_AEN_DRAIN));
+
+ if (count == TW_MAX_AEN_DRAIN)
+ goto out;
+
+ retval = 0;
+out:
+ tw_dev->state[request_id] = TW_S_INITIAL;
+ return retval;
+} /* End twa_aen_drain_queue() */
+
+/* This function will queue an event */
+static void twa_aen_queue_event(TW_Device_Extension *tw_dev, TW_Command_Apache_Header *header)
+{
+ u32 local_time;
+ struct timeval time;
+ TW_Event *event;
+ unsigned short aen;
+ char host[16];
+
+ tw_dev->aen_count++;
+
+ /* Fill out event info */
+ event = tw_dev->event_queue[tw_dev->error_index];
+
+ /* Check for clobber */
+ host[0] = '\0';
+ if (tw_dev->host) {
+ sprintf(host, " scsi%d:", tw_dev->host->host_no);
+ if (event->retrieved == TW_AEN_NOT_RETRIEVED)
+ tw_dev->aen_clobber = 1;
+ }
+
+ aen = header->status_block.error;
+ memset(event, 0, sizeof(TW_Event));
+
+ event->severity = header->status_block.substatus_block.severity;
+ do_gettimeofday(&time);
+ local_time = (u32)(time.tv_sec - (sys_tz.tz_minuteswest * 60));
+ event->time_stamp_sec = local_time;
+ event->aen_code = aen;
+ event->retrieved = TW_AEN_NOT_RETRIEVED;
+ event->sequence_id = tw_dev->error_sequence_id;
+ tw_dev->error_sequence_id++;
+
+ header->err_specific_desc[sizeof(header->err_specific_desc) - 1] = '\0';
+ event->parameter_len = strlen(header->err_specific_desc);
+ memcpy(event->parameter_data, header->err_specific_desc, event->parameter_len);
+ if (event->severity != TW_AEN_SEVERITY_DEBUG)
+ printk(KERN_WARNING "3w-9xxx:%s AEN: %s (0x%02X:0x%04X): %s:%s.\n", host, twa_aen_severity_lookup(header->status_block.substatus_block.severity), TW_MESSAGE_SOURCE_CONTROLLER_EVENT, aen, twa_string_lookup(twa_aen_table, aen), header->err_specific_desc);
+ else
+ tw_dev->aen_count--;
+
+ if ((tw_dev->error_index + 1) == TW_Q_LENGTH)
+ tw_dev->event_queue_wrapped = 1;
+ tw_dev->error_index = (tw_dev->error_index + 1 ) % TW_Q_LENGTH;
+} /* End twa_aen_queue_event() */
+
+/* This function will read the aen queue from the isr */
+static int twa_aen_read_queue(TW_Device_Extension *tw_dev, int request_id)
+{
+ char cdb[TW_MAX_CDB_LEN];
+ TW_SG_Apache sglist[1];
+ TW_Command_Full *full_command_packet;
+ int retval = 1;
+
+ full_command_packet = (TW_Command_Full *)tw_dev->command_packet_virt[request_id];
+ memset(full_command_packet, 0, sizeof(TW_Command_Full));
+
+ /* Initialize cdb */
+ memset(&cdb, 0, TW_MAX_CDB_LEN);
+ cdb[0] = REQUEST_SENSE; /* opcode */
+ cdb[4] = TW_ALLOCATION_LENGTH; /* allocation length */
+
+ /* Initialize sglist */
+ memset(&sglist, 0, sizeof(TW_SG_Apache));
+ sglist[0].length = TW_SECTOR_SIZE;
+ sglist[0].address = tw_dev->generic_buffer_phys[request_id];
+
+ /* Mark internal command */
+ tw_dev->srb[request_id] = NULL;
+
+ /* Now post the command packet */
+ if (twa_scsiop_execute_scsi(tw_dev, request_id, cdb, 1, sglist)) {
+ TW_PRINTK(tw_dev->host, TW_DRIVER, 0x4, "Post failed while reading AEN queue");
+ goto out;
+ }
+ retval = 0;
+out:
+ return retval;
+} /* End twa_aen_read_queue() */
+
+/* This function will look up an AEN severity string */
+static char *twa_aen_severity_lookup(unsigned char severity_code)
+{
+ char *retval = NULL;
+
+ if ((severity_code < (unsigned char) TW_AEN_SEVERITY_ERROR) ||
+ (severity_code > (unsigned char) TW_AEN_SEVERITY_DEBUG))
+ goto out;
+
+ retval = twa_aen_severity_table[severity_code];
+out:
+ return retval;
+} /* End twa_aen_severity_lookup() */
+
+/* This function will sync firmware time with the host time */
+static void twa_aen_sync_time(TW_Device_Extension *tw_dev, int request_id)
+{
+ u32 schedulertime;
+ struct timeval utc;
+ TW_Command_Full *full_command_packet;
+ TW_Command *command_packet;
+ TW_Param_Apache *param;
+ u32 local_time;
+
+ /* Fill out the command packet */
+ full_command_packet = (TW_Command_Full *)tw_dev->command_packet_virt[request_id];
+ memset(full_command_packet, 0, sizeof(TW_Command_Full));
+ command_packet = &full_command_packet->command.oldcommand;
+ command_packet->byte0_offset.opcode = TW_OP_SET_PARAM;
+ command_packet->byte0_offset.sgl_offset = 2;
+ command_packet->request_id = request_id;
+ command_packet->byte8_offset.param.sgl[0].address = tw_dev->generic_buffer_phys[request_id];
+ command_packet->byte8_offset.param.sgl[0].length = TW_SECTOR_SIZE;
+ command_packet->size = TW_COMMAND_SIZE;
+ command_packet->byte6_offset.parameter_count = 1;
+
+ /* Setup the param */
+ param = (TW_Param_Apache *)tw_dev->generic_buffer_virt[request_id];
+ memset(param, 0, TW_SECTOR_SIZE);
+ param->table_id = TW_TIMEKEEP_TABLE | 0x8000; /* Controller time keep table */
+ param->parameter_id = 0x3; /* SchedulerTime */
+ param->parameter_size_bytes = 4;
+
+ /* Convert system time in UTC to local time seconds since last
+ Sunday 12:00AM */
+ do_gettimeofday(&utc);
+ local_time = (u32)(utc.tv_sec - (sys_tz.tz_minuteswest * 60));
+ schedulertime = local_time - (3 * 86400);
+ schedulertime = schedulertime % 604800;
+
+ memcpy(param->data, &schedulertime, sizeof(u32));
+
+ /* Mark internal command */
+ tw_dev->srb[request_id] = NULL;
+
+ /* Now post the command */
+ twa_post_command_packet(tw_dev, request_id);
+} /* End twa_aen_sync_time() */
+
+/* This function will allocate memory and check if it is correctly aligned */
+static int twa_allocate_memory(TW_Device_Extension *tw_dev, int size, int which)
+{
+ int i;
+ dma_addr_t dma_handle;
+ unsigned long *cpu_addr;
+ int retval = 1;
+
+ cpu_addr = pci_alloc_consistent(tw_dev->tw_pci_dev, size*TW_Q_LENGTH, &dma_handle);
+ if (!cpu_addr) {
+ TW_PRINTK(tw_dev->host, TW_DRIVER, 0x5, "Memory allocation failed");
+ goto out;
+ }
+
+ if ((unsigned long)cpu_addr % (TW_ALIGNMENT_9000)) {
+ TW_PRINTK(tw_dev->host, TW_DRIVER, 0x6, "Failed to allocate correctly aligned memory");
+ pci_free_consistent(tw_dev->tw_pci_dev, size*TW_Q_LENGTH, cpu_addr, dma_handle);
+ goto out;
+ }
+
+ memset(cpu_addr, 0, size*TW_Q_LENGTH);
+
+ for (i = 0; i < TW_Q_LENGTH; i++) {
+ switch(which) {
+ case 0:
+ tw_dev->command_packet_phys[i] = dma_handle+(i*size);
+ tw_dev->command_packet_virt[i] = (unsigned long *)((unsigned char *)cpu_addr + (i*size));
+ break;
+ case 1:
+ tw_dev->generic_buffer_phys[i] = dma_handle+(i*size);
+ tw_dev->generic_buffer_virt[i] = (unsigned long *)((unsigned char *)cpu_addr + (i*size));
+ break;
+ }
+ }
+ retval = 0;
+out:
+ return retval;
+} /* End twa_allocate_memory() */
+
+/* This function will check the status register for unexpected bits */
+static int twa_check_bits(u32 status_reg_value)
+{
+ int retval = 1;
+
+ if ((status_reg_value & TW_STATUS_EXPECTED_BITS) != TW_STATUS_EXPECTED_BITS)
+ goto out;
+ if ((status_reg_value & TW_STATUS_UNEXPECTED_BITS) != 0)
+ goto out;
+
+ retval = 0;
+out:
+ return retval;
+} /* End twa_check_bits() */
+
+/* This function will check the srl and decide if we are compatible */
+static int twa_check_srl(TW_Device_Extension *tw_dev, int *flashed)
+{
+ int retval = 1;
+ unsigned short fw_on_ctlr_srl = 0, fw_on_ctlr_arch_id = 0;
+ unsigned short fw_on_ctlr_branch = 0, fw_on_ctlr_build = 0;
+ u32 init_connect_result = 0;
+
+ if (twa_initconnection(tw_dev, TW_INIT_MESSAGE_CREDITS, TW_EXTENDED_INIT_CONNECT, TW_CURRENT_FW_SRL, TW_9000_ARCH_ID, TW_CURRENT_FW_BRANCH, TW_CURRENT_FW_BUILD, &fw_on_ctlr_srl, &fw_on_ctlr_arch_id, &fw_on_ctlr_branch, &fw_on_ctlr_build, &init_connect_result)) {
+ TW_PRINTK(tw_dev->host, TW_DRIVER, 0x7, "Initconnection failed while checking SRL");
+ goto out;
+ }
+
+ tw_dev->working_srl = TW_CURRENT_FW_SRL;
+ tw_dev->working_branch = TW_CURRENT_FW_BRANCH;
+ tw_dev->working_build = TW_CURRENT_FW_BUILD;
+
+ /* Try base mode compatibility */
+ if (!(init_connect_result & TW_CTLR_FW_COMPATIBLE)) {
+ if (twa_initconnection(tw_dev, TW_INIT_MESSAGE_CREDITS, TW_EXTENDED_INIT_CONNECT, TW_BASE_FW_SRL, TW_9000_ARCH_ID, TW_BASE_FW_BRANCH, TW_BASE_FW_BUILD, &fw_on_ctlr_srl, &fw_on_ctlr_arch_id, &fw_on_ctlr_branch, &fw_on_ctlr_build, &init_connect_result)) {
+ TW_PRINTK(tw_dev->host, TW_DRIVER, 0xa, "Initconnection (base mode) failed while checking SRL");
+ goto out;
+ }
+ if (!(init_connect_result & TW_CTLR_FW_COMPATIBLE)) {
+ if (TW_CURRENT_FW_SRL > fw_on_ctlr_srl) {
+ TW_PRINTK(tw_dev->host, TW_DRIVER, 0x32, "Firmware and driver incompatibility: please upgrade firmware");
+ } else {
+ TW_PRINTK(tw_dev->host, TW_DRIVER, 0x33, "Firmware and driver incompatibility: please upgrade driver");
+ }
+ goto out;
+ }
+ tw_dev->working_srl = TW_BASE_FW_SRL;
+ tw_dev->working_branch = TW_BASE_FW_BRANCH;
+ tw_dev->working_build = TW_BASE_FW_BUILD;
+ }
+ retval = 0;
+out:
+ return retval;
+} /* End twa_check_srl() */
+
+/* This function handles ioctl for the character device */
+static int twa_chrdev_ioctl(struct inode *inode, struct file *file, unsigned int cmd, unsigned long arg)
+{
+ long timeout;
+ unsigned long *cpu_addr, data_buffer_length_adjusted = 0, flags = 0;
+ dma_addr_t dma_handle;
+ int request_id = 0;
+ unsigned int sequence_id = 0;
+ unsigned char event_index, start_index;
+ TW_Ioctl_Driver_Command driver_command;
+ TW_Ioctl_Buf_Apache *tw_ioctl;
+ TW_Lock *tw_lock;
+ TW_Command_Full *full_command_packet;
+ TW_Compatibility_Info *tw_compat_info;
+ TW_Event *event;
+ struct timeval current_time;
+ u32 current_time_ms;
+ TW_Device_Extension *tw_dev = twa_device_extension_list[MINOR(inode->i_rdev)];
+ int retval = TW_IOCTL_ERROR_OS_EFAULT;
+
+ /* Only let one of these through at a time */
+ if (down_interruptible(&tw_dev->ioctl_sem)) {
+ retval = TW_IOCTL_ERROR_OS_EINTR;
+ goto out;
+ }
+
+ /* First copy down the driver command */
+ if (copy_from_user(&driver_command, (void *)arg, sizeof(TW_Ioctl_Driver_Command)))
+ goto out2;
+
+ /* Check data buffer size */
+ if (driver_command.buffer_length > TW_MAX_SECTORS * 512) {
+ retval = TW_IOCTL_ERROR_OS_EINVAL;
+ goto out2;
+ }
+
+ /* Hardware can only do multiple of 512 byte transfers */
+ data_buffer_length_adjusted = (driver_command.buffer_length + 511) & ~511;
+
+ /* Now allocate ioctl buf memory */
+ cpu_addr = pci_alloc_consistent(tw_dev->tw_pci_dev, data_buffer_length_adjusted+sizeof(TW_Ioctl_Buf_Apache) - 1, &dma_handle);
+ if (!cpu_addr) {
+ retval = TW_IOCTL_ERROR_OS_ENOMEM;
+ goto out2;
+ }
+
+ tw_ioctl = (TW_Ioctl_Buf_Apache *)cpu_addr;
+
+ /* Now copy down the entire ioctl */
+ if (copy_from_user(tw_ioctl, (void *)arg, driver_command.buffer_length + sizeof(TW_Ioctl_Buf_Apache) - 1))
+ goto out3;
+
+ /* See which ioctl we are doing */
+ switch (cmd) {
+ case TW_IOCTL_FIRMWARE_PASS_THROUGH:
+ spin_lock_irqsave(tw_dev->host->host_lock, flags);
+ twa_get_request_id(tw_dev, &request_id);
+
+ /* Flag internal command */
+ tw_dev->srb[request_id] = 0;
+
+ /* Flag chrdev ioctl */
+ tw_dev->chrdev_request_id = request_id;
+
+ full_command_packet = &tw_ioctl->firmware_command;
+
+ /* Load request id and sglist for both command types */
+ twa_load_sgl(full_command_packet, request_id, dma_handle, data_buffer_length_adjusted);
+
+ memcpy(tw_dev->command_packet_virt[request_id], &(tw_ioctl->firmware_command), sizeof(TW_Command_Full));
+
+ /* Now post the command packet to the controller */
+ twa_post_command_packet(tw_dev, request_id);
+ spin_unlock_irqrestore(tw_dev->host->host_lock, flags);
+
+ timeout = TW_IOCTL_CHRDEV_TIMEOUT*HZ;
+
+ /* Now wait for command to complete */
+ timeout = wait_event_interruptible_timeout(tw_dev->ioctl_wqueue, tw_dev->chrdev_request_id == TW_IOCTL_CHRDEV_FREE, timeout);
+
+ /* Check if we timed out, got a signal, or didn't get
+ an interrupt */
+ if ((timeout <= 0) && (tw_dev->chrdev_request_id != TW_IOCTL_CHRDEV_FREE)) {
+ /* Now we need to reset the board */
+ if (timeout == TW_IOCTL_ERROR_OS_ERESTARTSYS) {
+ retval = timeout;
+ } else {
+ printk(KERN_WARNING "3w-9xxx: scsi%d: WARNING: (0x%02X:0x%04X): Character ioctl (0x%x) timed out, resetting card.\n", tw_dev->host->host_no, TW_DRIVER, 0xc, cmd);
+ retval = TW_IOCTL_ERROR_OS_EIO;
+ }
+ spin_lock_irqsave(tw_dev->host->host_lock, flags);
+ tw_dev->state[request_id] = TW_S_COMPLETED;
+ twa_free_request_id(tw_dev, request_id);
+ tw_dev->posted_request_count--;
+ twa_reset_device_extension(tw_dev);
+ spin_unlock_irqrestore(tw_dev->host->host_lock, flags);
+ goto out3;
+ }
+
+ /* Now copy in the command packet response */
+ memcpy(&(tw_ioctl->firmware_command), tw_dev->command_packet_virt[request_id], sizeof(TW_Command_Full));
+
+ /* Now complete the io */
+ spin_lock_irqsave(tw_dev->host->host_lock, flags);
+ tw_dev->posted_request_count--;
+ tw_dev->state[request_id] = TW_S_COMPLETED;
+ twa_free_request_id(tw_dev, request_id);
+ spin_unlock_irqrestore(tw_dev->host->host_lock, flags);
+ break;
+ case TW_IOCTL_GET_COMPATIBILITY_INFO:
+ tw_ioctl->driver_command.status = 0;
+ /* Copy compatiblity struct into ioctl data buffer */
+ tw_compat_info = (TW_Compatibility_Info *)tw_ioctl->data_buffer;
+ strncpy(tw_compat_info->driver_version, twa_driver_version, strlen(twa_driver_version));
+ tw_compat_info->working_srl = tw_dev->working_srl;
+ tw_compat_info->working_branch = tw_dev->working_branch;
+ tw_compat_info->working_build = tw_dev->working_build;
+ break;
+ case TW_IOCTL_GET_LAST_EVENT:
+ if (tw_dev->event_queue_wrapped) {
+ if (tw_dev->aen_clobber) {
+ tw_ioctl->driver_command.status = TW_IOCTL_ERROR_STATUS_AEN_CLOBBER;
+ tw_dev->aen_clobber = 0;
+ } else
+ tw_ioctl->driver_command.status = 0;
+ } else {
+ if (!tw_dev->error_index) {
+ tw_ioctl->driver_command.status = TW_IOCTL_ERROR_STATUS_NO_MORE_EVENTS;
+ break;
+ }
+ tw_ioctl->driver_command.status = 0;
+ }
+ event_index = (tw_dev->error_index - 1 + TW_Q_LENGTH) % TW_Q_LENGTH;
+ memcpy(tw_ioctl->data_buffer, tw_dev->event_queue[event_index], sizeof(TW_Event));
+ tw_dev->event_queue[event_index]->retrieved = TW_AEN_RETRIEVED;
+ break;
+ case TW_IOCTL_GET_FIRST_EVENT:
+ if (tw_dev->event_queue_wrapped) {
+ if (tw_dev->aen_clobber) {
+ tw_ioctl->driver_command.status = TW_IOCTL_ERROR_STATUS_AEN_CLOBBER;
+ tw_dev->aen_clobber = 0;
+ } else
+ tw_ioctl->driver_command.status = 0;
+ event_index = tw_dev->error_index;
+ } else {
+ if (!tw_dev->error_index) {
+ tw_ioctl->driver_command.status = TW_IOCTL_ERROR_STATUS_NO_MORE_EVENTS;
+ break;
+ }
+ tw_ioctl->driver_command.status = 0;
+ event_index = 0;
+ }
+ memcpy(tw_ioctl->data_buffer, tw_dev->event_queue[event_index], sizeof(TW_Event));
+ tw_dev->event_queue[event_index]->retrieved = TW_AEN_RETRIEVED;
+ break;
+ case TW_IOCTL_GET_NEXT_EVENT:
+ event = (TW_Event *)tw_ioctl->data_buffer;
+ sequence_id = event->sequence_id;
+ tw_ioctl->driver_command.status = 0;
+
+ if (tw_dev->event_queue_wrapped) {
+ if (tw_dev->aen_clobber) {
+ tw_ioctl->driver_command.status = TW_IOCTL_ERROR_STATUS_AEN_CLOBBER;
+ tw_dev->aen_clobber = 0;
+ }
+ start_index = tw_dev->error_index;
+ } else {
+ if (!tw_dev->error_index) {
+ tw_ioctl->driver_command.status = TW_IOCTL_ERROR_STATUS_NO_MORE_EVENTS;
+ break;
+ }
+ start_index = 0;
+ }
+ event_index = (start_index + sequence_id - tw_dev->event_queue[start_index]->sequence_id + 1) % TW_Q_LENGTH;
+
+ if (!(tw_dev->event_queue[event_index]->sequence_id > sequence_id)) {
+ if (tw_ioctl->driver_command.status == TW_IOCTL_ERROR_STATUS_AEN_CLOBBER)
+ tw_dev->aen_clobber = 1;
+ tw_ioctl->driver_command.status = TW_IOCTL_ERROR_STATUS_NO_MORE_EVENTS;
+ break;
+ }
+ memcpy(tw_ioctl->data_buffer, tw_dev->event_queue[event_index], sizeof(TW_Event));
+ tw_dev->event_queue[event_index]->retrieved = TW_AEN_RETRIEVED;
+ break;
+ case TW_IOCTL_GET_PREVIOUS_EVENT:
+ event = (TW_Event *)tw_ioctl->data_buffer;
+ sequence_id = event->sequence_id;
+ tw_ioctl->driver_command.status = 0;
+
+ if (tw_dev->event_queue_wrapped) {
+ if (tw_dev->aen_clobber) {
+ tw_ioctl->driver_command.status = TW_IOCTL_ERROR_STATUS_AEN_CLOBBER;
+ tw_dev->aen_clobber = 0;
+ }
+ start_index = tw_dev->error_index;
+ } else {
+ if (!tw_dev->error_index) {
+ tw_ioctl->driver_command.status = TW_IOCTL_ERROR_STATUS_NO_MORE_EVENTS;
+ break;
+ }
+ start_index = 0;
+ }
+ event_index = (start_index + sequence_id - tw_dev->event_queue[start_index]->sequence_id - 1) % TW_Q_LENGTH;
+
+ if (!(tw_dev->event_queue[event_index]->sequence_id < sequence_id)) {
+ if (tw_ioctl->driver_command.status == TW_IOCTL_ERROR_STATUS_AEN_CLOBBER)
+ tw_dev->aen_clobber = 1;
+ tw_ioctl->driver_command.status = TW_IOCTL_ERROR_STATUS_NO_MORE_EVENTS;
+ break;
+ }
+ memcpy(tw_ioctl->data_buffer, tw_dev->event_queue[event_index], sizeof(TW_Event));
+ tw_dev->event_queue[event_index]->retrieved = TW_AEN_RETRIEVED;
+ break;
+ case TW_IOCTL_GET_LOCK:
+ tw_lock = (TW_Lock *)tw_ioctl->data_buffer;
+ do_gettimeofday(¤t_time);
+ current_time_ms = (current_time.tv_sec * 1000) + (current_time.tv_usec / 1000);
+
+ if ((tw_lock->force_flag == 1) || (tw_dev->ioctl_sem_lock == 0) || (current_time_ms >= tw_dev->ioctl_msec)) {
+ tw_dev->ioctl_sem_lock = 1;
+ tw_dev->ioctl_msec = current_time_ms + tw_lock->timeout_msec;
+ tw_ioctl->driver_command.status = 0;
+ tw_lock->time_remaining_msec = tw_lock->timeout_msec;
+ } else {
+ tw_ioctl->driver_command.status = TW_IOCTL_ERROR_STATUS_LOCKED;
+ tw_lock->time_remaining_msec = tw_dev->ioctl_msec - current_time_ms;
+ }
+ break;
+ case TW_IOCTL_RELEASE_LOCK:
+ if (tw_dev->ioctl_sem_lock == 1) {
+ tw_dev->ioctl_sem_lock = 0;
+ tw_ioctl->driver_command.status = 0;
+ } else {
+ tw_ioctl->driver_command.status = TW_IOCTL_ERROR_STATUS_NOT_LOCKED;
+ }
+ break;
+ default:
+ retval = TW_IOCTL_ERROR_OS_ENOTTY;
+ goto out3;
+ }
+
+ /* Now copy the entire response to userspace */
+ if (copy_to_user((void *)arg, tw_ioctl, sizeof(TW_Ioctl_Buf_Apache) + driver_command.buffer_length - 1) == 0)
+ retval = 0;
+out3:
+ /* Now free ioctl buf memory */
+ pci_free_consistent(tw_dev->tw_pci_dev, data_buffer_length_adjusted+sizeof(TW_Ioctl_Buf_Apache) - 1, cpu_addr, dma_handle);
+out2:
+ up(&tw_dev->ioctl_sem);
+out:
+ return retval;
+} /* End twa_chrdev_ioctl() */
+
+/* This function handles open for the character device */
+static int twa_chrdev_open(struct inode *inode, struct file *file)
+{
+ unsigned int minor_number;
+ int retval = TW_IOCTL_ERROR_OS_ENODEV;
+
+ minor_number = MINOR(inode->i_rdev);
+ if (minor_number >= twa_device_extension_count)
+ goto out;
+ retval = 0;
+out:
+ return retval;
+} /* End twa_chrdev_open() */
+
+/* This function handles close for the character device */
+static int twa_chrdev_release(struct inode *inode, struct file *file)
+{
+ return 0;
+} /* End twa_chrdev_release() */
+
+/* This function is called by twa_scsi_proc_info */
+static int twa_copy_info(TW_Info *info, char *fmt, ...)
+{
+ va_list args;
+ char buf[81];
+ int len;
+
+ va_start(args, fmt);
+ len = vsprintf(buf, fmt, args);
+ va_end(args);
+ twa_copy_mem_info(info, buf, len);
+
+ return len;
+} /* End twa_copy_info() */
+
+/* This function is called by twa_scsi_proc_info */
+static void twa_copy_mem_info(TW_Info *info, char *data, int len)
+{
+ if (info->position + len > info->length)
+ len = info->length - info->position;
+
+ if (info->position + len < info->offset) {
+ info->position += len;
+ return;
+ }
+ if (info->position < info->offset) {
+ data += (info->offset - info->position);
+ len -= (info->offset - info->position);
+ }
+ if (len > 0) {
+ memcpy(info->buffer + info->position, data, len);
+ info->position += len;
+ }
+} /* End twa_copy_mem_info() */
+
+/* This function will print readable messages from status register errors */
+static int twa_decode_bits(TW_Device_Extension *tw_dev, u32 status_reg_value)
+{
+ int retval = 1;
+
+ /* Check for various error conditions and handle them appropriately */
+ if (status_reg_value & TW_STATUS_PCI_PARITY_ERROR) {
+ TW_PRINTK(tw_dev->host, TW_DRIVER, 0xc, "PCI Parity Error: clearing");
+ writel(TW_CONTROL_CLEAR_PARITY_ERROR, TW_CONTROL_REG_ADDR(tw_dev));
+ }
+
+ if (status_reg_value & TW_STATUS_PCI_ABORT) {
+ TW_PRINTK(tw_dev->host, TW_DRIVER, 0xd, "PCI Abort: clearing");
+ writel(TW_CONTROL_CLEAR_PCI_ABORT, TW_CONTROL_REG_ADDR(tw_dev));
+ pci_write_config_word(tw_dev->tw_pci_dev, PCI_STATUS, TW_PCI_CLEAR_PCI_ABORT);
+ }
+
+ if (status_reg_value & TW_STATUS_QUEUE_ERROR) {
+ TW_PRINTK(tw_dev->host, TW_DRIVER, 0xe, "Controller Queue Error: clearing");
+ writel(TW_CONTROL_CLEAR_QUEUE_ERROR, TW_CONTROL_REG_ADDR(tw_dev));
+ }
+
+ if (status_reg_value & TW_STATUS_SBUF_WRITE_ERROR) {
+ TW_PRINTK(tw_dev->host, TW_DRIVER, 0xf, "SBUF Write Error: clearing");
+ writel(TW_CONTROL_CLEAR_SBUF_WRITE_ERROR, TW_CONTROL_REG_ADDR(tw_dev));
+ }
+
+ if (status_reg_value & TW_STATUS_MICROCONTROLLER_ERROR) {
+ if (tw_dev->reset_print == 0) {
+ TW_PRINTK(tw_dev->host, TW_DRIVER, 0x10, "Microcontroller Error: clearing");
+ tw_dev->reset_print = 1;
+ }
+ goto out;
+ }
+ retval = 0;
+out:
+ return retval;
+} /* End twa_decode_bits() */
+
+/* This function will empty the response queue */
+static int twa_empty_response_queue(TW_Device_Extension *tw_dev)
+{
+ u32 status_reg_value, response_que_value;
+ int count = 0, retval = 1;
+
+ status_reg_value = readl(TW_STATUS_REG_ADDR(tw_dev));
+
+ while (((status_reg_value & TW_STATUS_RESPONSE_QUEUE_EMPTY) == 0) && (count < TW_MAX_RESPONSE_DRAIN)) {
+ response_que_value = readl(TW_RESPONSE_QUEUE_REG_ADDR(tw_dev));
+ status_reg_value = readl(TW_STATUS_REG_ADDR(tw_dev));
+ count++;
+ }
+ if (count == TW_MAX_RESPONSE_DRAIN)
+ goto out;
+
+ retval = 0;
+out:
+ return retval;
+} /* End twa_empty_response_queue() */
+
+/* This function passes sense keys from firmware to scsi layer */
+static int twa_fill_sense(TW_Device_Extension *tw_dev, int request_id, int copy_sense, int print_host)
+{
+ TW_Command_Full *full_command_packet;
+ unsigned short error;
+ int retval = 1;
+
+ full_command_packet = (TW_Command_Full *)tw_dev->command_packet_virt[request_id];
+ /* Don't print error for Logical unit not supported during rollcall */
+ error = full_command_packet->header.status_block.error;
+ if ((error != TW_ERROR_LOGICAL_UNIT_NOT_SUPPORTED) && (error != TW_ERROR_UNIT_OFFLINE)) {
+ if (print_host)
+ printk(KERN_WARNING "3w-9xxx: scsi%d: ERROR: (0x%02X:0x%04X): %s:%s.\n", tw_dev->host->host_no, TW_MESSAGE_SOURCE_CONTROLLER_ERROR, full_command_packet->header.status_block.error, twa_string_lookup(twa_error_table, full_command_packet->header.status_block.error), full_command_packet->header.err_specific_desc);
+ else
+ printk(KERN_WARNING "3w-9xxx: ERROR: (0x%02X:0x%04X): %s:%s.\n", TW_MESSAGE_SOURCE_CONTROLLER_ERROR, full_command_packet->header.status_block.error, twa_string_lookup(twa_error_table, full_command_packet->header.status_block.error), full_command_packet->header.err_specific_desc);
+ }
+
+ if (copy_sense) {
+ memcpy(tw_dev->srb[request_id]->sense_buffer, full_command_packet->header.sense_data, TW_SENSE_DATA_LENGTH);
+ tw_dev->srb[request_id]->result = (full_command_packet->command.newcommand.status << 1);
+ retval = TW_ISR_DONT_RESULT;
+ goto out;
+ }
+ retval = 0;
+out:
+ return retval;
+} /* End twa_fill_sense() */
+
+/* This function will free up device extension resources */
+static void twa_free_device_extension(TW_Device_Extension *tw_dev)
+{
+ if (tw_dev->command_packet_virt[0])
+ pci_free_consistent(tw_dev->tw_pci_dev, sizeof(TW_Command_Full)*TW_Q_LENGTH, tw_dev->command_packet_virt[0], tw_dev->command_packet_phys[0]);
+
+ if (tw_dev->generic_buffer_virt[0])
+ pci_free_consistent(tw_dev->tw_pci_dev, TW_SECTOR_SIZE*TW_Q_LENGTH, tw_dev->generic_buffer_virt[0], tw_dev->generic_buffer_phys[0]);
+
+ if (tw_dev->event_queue[0])
+ kfree(tw_dev->event_queue[0]);
+} /* End twa_free_device_extension() */
+
+/* This function will free a request id */
+static void twa_free_request_id(TW_Device_Extension *tw_dev, int request_id)
+{
+ tw_dev->free_queue[tw_dev->free_tail] = request_id;
+ tw_dev->state[request_id] = TW_S_FINISHED;
+ tw_dev->free_tail = (tw_dev->free_tail + 1) % TW_Q_LENGTH;
+} /* End twa_free_request_id() */
+
+/* This function will get parameter table entires from the firmware */
+static void *twa_get_param(TW_Device_Extension *tw_dev, int request_id, int table_id, int parameter_id, int parameter_size_bytes)
+{
+ TW_Command_Full *full_command_packet;
+ TW_Command *command_packet;
+ TW_Param_Apache *param;
+ unsigned long param_value;
+ void *retval = NULL;
+
+ /* Setup the command packet */
+ full_command_packet = (TW_Command_Full *)tw_dev->command_packet_virt[request_id];
+ memset(full_command_packet, 0, sizeof(TW_Command_Full));
+ command_packet = &full_command_packet->command.oldcommand;
+
+ command_packet->byte0_offset.opcode = TW_OP_GET_PARAM;
+ command_packet->byte0_offset.sgl_offset = 2;
+ command_packet->size = TW_COMMAND_SIZE;
+ command_packet->request_id = request_id;
+ command_packet->byte6_offset.block_count = 1;
+
+ /* Now setup the param */
+ param = (TW_Param_Apache *)tw_dev->generic_buffer_virt[request_id];
+ memset(param, 0, TW_SECTOR_SIZE);
+ param->table_id = table_id | 0x8000;
+ param->parameter_id = parameter_id;
+ param->parameter_size_bytes = parameter_size_bytes;
+ param_value = tw_dev->generic_buffer_phys[request_id];
+
+ command_packet->byte8_offset.param.sgl[0].address = param_value;
+ command_packet->byte8_offset.param.sgl[0].length = TW_SECTOR_SIZE;
+
+ /* Post the command packet to the board */
+ twa_post_command_packet(tw_dev, request_id);
+
+ /* Poll for completion */
+ if (twa_poll_response(tw_dev, request_id, 30))
+ TW_PRINTK(tw_dev->host, TW_DRIVER, 0x13, "No valid response during get param")
+ else
+ retval = (void *)&(param->data[0]);
+
+ tw_dev->posted_request_count--;
+ tw_dev->state[request_id] = TW_S_INITIAL;
+
+ return retval;
+} /* End twa_get_param() */
+
+/* This function will assign an available request id */
+static void twa_get_request_id(TW_Device_Extension *tw_dev, int *request_id)
+{
+ *request_id = tw_dev->free_queue[tw_dev->free_head];
+ tw_dev->free_head = (tw_dev->free_head + 1) % TW_Q_LENGTH;
+ tw_dev->state[*request_id] = TW_S_STARTED;
+} /* End twa_get_request_id() */
+
+/* Clean shutdown routine */
+static int twa_halt(struct notifier_block *nb, ulong event, void *buf)
+{
+ unsigned int i;
+
+ for (i = 0; i < twa_device_extension_count; i++) {
+ if (twa_device_extension_list[i]->online == 1) {
+ printk(KERN_WARNING "3w-9xxx: Shutting down card %d.\n", i);
+ twa_shutdown_device(twa_device_extension_list[i]);
+ twa_device_extension_list[i]->online = 0;
+ }
+ }
+ unregister_reboot_notifier(&twa_notifier);
+
+ return NOTIFY_OK;
+} /* End twa_halt() */
+
+/* This function will send an initconnection command to controller */
+static int twa_initconnection(TW_Device_Extension *tw_dev, int message_credits,
+ u32 set_features, unsigned short current_fw_srl,
+ unsigned short current_fw_arch_id,
+ unsigned short current_fw_branch,
+ unsigned short current_fw_build,
+ unsigned short *fw_on_ctlr_srl,
+ unsigned short *fw_on_ctlr_arch_id,
+ unsigned short *fw_on_ctlr_branch,
+ unsigned short *fw_on_ctlr_build,
+ u32 *init_connect_result)
+{
+ TW_Command_Full *full_command_packet;
+ TW_Initconnect *tw_initconnect;
+ int request_id = 0, retval = 1;
+
+ /* Initialize InitConnection command packet */
+ full_command_packet = (TW_Command_Full *)tw_dev->command_packet_virt[request_id];
+ memset(full_command_packet, 0, sizeof(TW_Command_Full));
+ full_command_packet->header.header_desc.size_header = 128;
+
+ tw_initconnect = (TW_Initconnect *)&full_command_packet->command.oldcommand;
+ tw_initconnect->opcode = TW_OP_INIT_CONNECTION;
+
+ tw_initconnect->request_id = request_id;
+ tw_initconnect->message_credits = message_credits;
+ tw_initconnect->features = set_features;
+#if BITS_PER_LONG > 32
+ /* Turn on 64-bit sgl support */
+ tw_initconnect->features |= 1;
+#endif
+
+ if (set_features & TW_EXTENDED_INIT_CONNECT)
+ {
+ tw_initconnect->size = TW_INIT_COMMAND_PACKET_SIZE_EXTENDED;
+ tw_initconnect->fw_srl = current_fw_srl;
+ tw_initconnect->fw_arch_id = current_fw_arch_id;
+ tw_initconnect->fw_branch = current_fw_branch;
+ tw_initconnect->fw_build = current_fw_build;
+ } else
+ tw_initconnect->size = TW_INIT_COMMAND_PACKET_SIZE;
+
+ /* Send command packet to the board */
+ twa_post_command_packet(tw_dev, request_id);
+
+ /* Poll for completion */
+ if (twa_poll_response(tw_dev, request_id, 30)) {
+ TW_PRINTK(tw_dev->host, TW_DRIVER, 0x15, "No valid response during init connection");
+ } else {
+ if (set_features & TW_EXTENDED_INIT_CONNECT) {
+ *fw_on_ctlr_srl = tw_initconnect->fw_srl;
+ *fw_on_ctlr_arch_id = tw_initconnect->fw_arch_id;
+ *fw_on_ctlr_branch = tw_initconnect->fw_branch;
+ *fw_on_ctlr_build = tw_initconnect->fw_build;
+ *init_connect_result = tw_initconnect->result;
+ }
+ retval = 0;
+ }
+
+ tw_dev->posted_request_count--;
+ tw_dev->state[request_id] = TW_S_INITIAL;
+
+ return retval;
+} /* End twa_initconnection() */
+
+/* This function will initialize the fields of a device extension */
+static int twa_initialize_device_extension(TW_Device_Extension *tw_dev)
+{
+ int i, retval = 1;
+
+ /* Initialize command packet buffers */
+ if (twa_allocate_memory(tw_dev, sizeof(TW_Command_Full), 0)) {
+ TW_PRINTK(tw_dev->host, TW_DRIVER, 0x16, "Command packet memory allocation failed");
+ goto out;
+ }
+
+ /* Initialize generic buffer */
+ if (twa_allocate_memory(tw_dev, TW_SECTOR_SIZE, 1)) {
+ TW_PRINTK(tw_dev->host, TW_DRIVER, 0x17, "Generic memory allocation failed");
+ goto out;
+ }
+
+ /* Allocate event info space */
+ tw_dev->event_queue[0] = kmalloc(sizeof(TW_Event) * TW_Q_LENGTH, GFP_ATOMIC);
+ if (!tw_dev->event_queue[0]) {
+ TW_PRINTK(tw_dev->host, TW_DRIVER, 0x18, "Event info memory allocation failed");
+ goto out;
+ }
+
+ memset(tw_dev->event_queue[0], 0, sizeof(TW_Event) * TW_Q_LENGTH);
+
+ for (i = 0; i < TW_Q_LENGTH; i++) {
+ tw_dev->event_queue[i] = (TW_Event *)((unsigned char *)tw_dev->event_queue[0] + (i * sizeof(TW_Event)));
+ tw_dev->free_queue[i] = i;
+ tw_dev->state[i] = TW_S_INITIAL;
+ }
+
+ tw_dev->pending_head = TW_Q_START;
+ tw_dev->pending_tail = TW_Q_START;
+ tw_dev->free_head = TW_Q_START;
+ tw_dev->free_tail = TW_Q_START;
+ tw_dev->error_sequence_id = 1;
+ tw_dev->chrdev_request_id = TW_IOCTL_CHRDEV_FREE;
+ retval = 0;
+out:
+ return retval;
+} /* End twa_initialize_device_extension() */
+
+/* This function is the interrupt service routine */
+static irqreturn_t twa_interrupt(int irq, void *dev_instance, struct pt_regs *regs)
+{
+ int request_id, error = 0;
+ u32 status_reg_value;
+ TW_Response_Queue response_que;
+ unsigned long flags = 0;
+ TW_Command_Full *full_command_packet;
+ TW_Command *command_packet;
+ TW_Device_Extension *tw_dev = (TW_Device_Extension *)dev_instance;
+ int handled = 0;
+
+ /* See if we are already running on another processor */
+ if (test_and_set_bit(TW_IN_INTR, &tw_dev->flags))
+ return IRQ_NONE;
+
+ /* Get the per adapter lock */
+ spin_lock_irqsave(tw_dev->host->host_lock, flags);
+
+ /* See if the interrupt matches this instance */
+ if (tw_dev->tw_pci_dev->irq == (unsigned int)irq) {
+
+ handled = 1;
+
+ /* Read the registers */
+ status_reg_value = readl(TW_STATUS_REG_ADDR(tw_dev));
+
+ /* Check if this is our interrupt, otherwise bail */
+ if (!(status_reg_value & TW_STATUS_VALID_INTERRUPT))
+ goto twa_interrupt_bail;
+
+ /* Check controller for errors */
+ if (twa_check_bits(status_reg_value)) {
+ if (twa_decode_bits(tw_dev, status_reg_value)) {
+ TW_CLEAR_ALL_INTERRUPTS(tw_dev);
+ goto twa_interrupt_bail;
+ }
+ }
+
+ /* Handle host interrupt */
+ if (status_reg_value & TW_STATUS_HOST_INTERRUPT)
+ TW_CLEAR_HOST_INTERRUPT(tw_dev);
+
+ /* Handle attention interrupt */
+ if (status_reg_value & TW_STATUS_ATTENTION_INTERRUPT) {
+ TW_CLEAR_ATTENTION_INTERRUPT(tw_dev);
+ if (!(test_and_set_bit(TW_IN_ATTENTION_LOOP, &tw_dev->flags))) {
+ twa_get_request_id(tw_dev, &request_id);
+
+ error = twa_aen_read_queue(tw_dev, request_id);
+ if (error) {
+ tw_dev->state[request_id] = TW_S_COMPLETED;
+ twa_free_request_id(tw_dev, request_id);
+ clear_bit(TW_IN_ATTENTION_LOOP, &tw_dev->flags);
+ }
+ }
+ }
+
+ /* Handle command interrupt */
+ if (status_reg_value & TW_STATUS_COMMAND_INTERRUPT) {
+ TW_MASK_COMMAND_INTERRUPT(tw_dev);
+ /* Drain as many pending commands as we can */
+ while (tw_dev->pending_request_count > 0) {
+ request_id = tw_dev->pending_queue[tw_dev->pending_head];
+ if (tw_dev->state[request_id] != TW_S_PENDING) {
+ TW_PRINTK(tw_dev->host, TW_DRIVER, 0x19, "Found request id that wasn't pending");
+ TW_CLEAR_ALL_INTERRUPTS(tw_dev);
+ goto twa_interrupt_bail;
+ }
+ if (twa_post_command_packet(tw_dev, request_id)==0) {
+ tw_dev->pending_head = (tw_dev->pending_head + 1) % TW_Q_LENGTH;
+ tw_dev->pending_request_count--;
+ } else {
+ /* If we get here, we will continue re-posting on the next command interrupt */
+ break;
+ }
+ }
+ }
+
+ /* Handle response interrupt */
+ if (status_reg_value & TW_STATUS_RESPONSE_INTERRUPT) {
+
+ /* Drain the response queue from the board */
+ while ((status_reg_value & TW_STATUS_RESPONSE_QUEUE_EMPTY) == 0) {
+ /* Complete the response */
+ response_que.value = readl(TW_RESPONSE_QUEUE_REG_ADDR(tw_dev));
+ request_id = response_que.u.response_id;
+ full_command_packet = (TW_Command_Full *)tw_dev->command_packet_virt[request_id];
+ error = 0;
+ command_packet = &full_command_packet->command.oldcommand;
+ /* Check for command packet errors */
+ if (full_command_packet->command.newcommand.status != 0) {
+ if (tw_dev->srb[request_id] != 0) {
+ error = twa_fill_sense(tw_dev, request_id, 1, 1);
+ } else {
+ /* Skip ioctl error prints */
+ if (request_id != tw_dev->chrdev_request_id) {
+ error = twa_fill_sense(tw_dev, request_id, 0, 1);
+ }
+ }
+ }
+
+ /* Check for correct state */
+ if (tw_dev->state[request_id] != TW_S_POSTED) {
+ if (tw_dev->srb[request_id] != 0) {
+ TW_PRINTK(tw_dev->host, TW_DRIVER, 0x1a, "Received a request id that wasn't posted");
+ TW_CLEAR_ALL_INTERRUPTS(tw_dev);
+ goto twa_interrupt_bail;
+ }
+ }
+
+ /* Check for internal command completion */
+ if (tw_dev->srb[request_id] == 0) {
+ if (request_id != tw_dev->chrdev_request_id) {
+ if (twa_aen_complete(tw_dev, request_id))
+ TW_PRINTK(tw_dev->host, TW_DRIVER, 0x1b, "Error completing AEN during attention interrupt");
+ } else {
+ tw_dev->chrdev_request_id = TW_IOCTL_CHRDEV_FREE;
+ wake_up(&tw_dev->ioctl_wqueue);
+ }
+ } else {
+ twa_scsiop_execute_scsi_complete(tw_dev, request_id);
+ /* If no error command was a success */
+ if (error == 0) {
+ tw_dev->srb[request_id]->result = (DID_OK << 16);
+ }
+
+ /* If error, command failed */
+ if (error == 1) {
+ /* Ask for a host reset */
+ tw_dev->srb[request_id]->result = (DID_OK << 16) | (CHECK_CONDITION << 1);
+ }
+
+ /* Now complete the io */
+ tw_dev->state[request_id] = TW_S_COMPLETED;
+ twa_free_request_id(tw_dev, request_id);
+ tw_dev->posted_request_count--;
+ tw_dev->srb[request_id]->scsi_done(tw_dev->srb[request_id]);
+ twa_unmap_scsi_data(tw_dev, request_id);
+ }
+
+ /* Check for valid status after each drain */
+ status_reg_value = readl(TW_STATUS_REG_ADDR(tw_dev));
+ if (twa_check_bits(status_reg_value)) {
+ if (twa_decode_bits(tw_dev, status_reg_value)) {
+ TW_CLEAR_ALL_INTERRUPTS(tw_dev);
+ goto twa_interrupt_bail;
+ }
+ }
+ }
+ }
+ }
+twa_interrupt_bail:
+ spin_unlock_irqrestore(tw_dev->host->host_lock, flags);
+ clear_bit(TW_IN_INTR, &tw_dev->flags);
+ return IRQ_RETVAL(handled);
+} /* End twa_interrupt() */
+
+/* This function will load the request id and various sgls for ioctls */
+static void twa_load_sgl(TW_Command_Full *full_command_packet, int request_id, dma_addr_t dma_handle, int length)
+{
+ TW_Command *oldcommand;
+ TW_Command_Apache *newcommand;
+ TW_SG_Entry *sgl;
+
+ if (full_command_packet->command.newcommand.command.opcode == TW_OP_EXECUTE_SCSI) {
+ newcommand = &full_command_packet->command.newcommand;
+ newcommand->request_id = request_id;
+ newcommand->sg_list[0].address = dma_handle + sizeof(TW_Ioctl_Buf_Apache) - 1;
+ newcommand->sg_list[0].length = length;
+ } else {
+ oldcommand = &full_command_packet->command.oldcommand;
+ oldcommand->request_id = request_id;
+
+ if (oldcommand->byte0_offset.sgl_offset) {
+ /* Load the sg list */
+ sgl = (TW_SG_Entry *)((u32 *)oldcommand+oldcommand->byte0_offset.sgl_offset);
+ sgl->address = dma_handle + sizeof(TW_Ioctl_Buf_Apache) - 1;
+ sgl->length = length;
+ }
+ }
+} /* End twa_load_sgl() */
+
+/* This function will perform a pci-dma mapping for a scatter gather list */
+static int twa_map_scsi_sg_data(TW_Device_Extension *tw_dev, int request_id)
+{
+ int use_sg;
+ Scsi_Cmnd *cmd = tw_dev->srb[request_id];
+ int dma_dir = scsi_to_pci_dma_dir(cmd->sc_data_direction);
+ struct pci_dev *pdev = tw_dev->tw_pci_dev;
+ int retval = 0;
+
+ if (cmd->use_sg == 0)
+ goto out;
+
+ use_sg = pci_map_sg(pdev, cmd->buffer, cmd->use_sg, dma_dir);
+
+ if (use_sg == 0) {
+ TW_PRINTK(tw_dev->host, TW_DRIVER, 0x1c, "Failed to map scatter gather list");
+ goto out;
+ }
+
+ cmd->SCp.phase = 2;
+ cmd->SCp.have_data_in = use_sg;
+ retval = use_sg;
+out:
+ return retval;
+} /* End twa_map_scsi_sg_data() */
+
+/* This function will perform a pci-dma map for a single buffer */
+static dma_addr_t twa_map_scsi_single_data(TW_Device_Extension *tw_dev, int request_id)
+{
+ dma_addr_t mapping;
+ Scsi_Cmnd *cmd = tw_dev->srb[request_id];
+ int dma_dir = scsi_to_pci_dma_dir(cmd->sc_data_direction);
+ struct pci_dev *pdev = tw_dev->tw_pci_dev;
+ int retval = 0;
+
+ if (cmd->request_bufflen == 0) {
+ retval = 0;
+ goto out;
+ }
+
+ mapping = pci_map_single(pdev, cmd->request_buffer, cmd->request_bufflen, dma_dir);
+
+ if (mapping == 0) {
+ TW_PRINTK(tw_dev->host, TW_DRIVER, 0x1d, "Failed to map page");
+ goto out;
+ }
+
+ cmd->SCp.phase = 1;
+ cmd->SCp.have_data_in = mapping;
+ retval = mapping;
+out:
+ return retval;
+} /* End twa_map_scsi_single_data() */
+
+/* This function will poll for a response interrupt of a request */
+static int twa_poll_response(TW_Device_Extension *tw_dev, int request_id, int seconds)
+{
+ int retval = 1, found = 0, response_request_id;
+ TW_Response_Queue response_queue;
+ TW_Command_Full *full_command_packet = (TW_Command_Full *)tw_dev->command_packet_virt[request_id];
+
+ if (twa_poll_status_gone(tw_dev, TW_STATUS_RESPONSE_QUEUE_EMPTY, seconds) == 0) {
+ response_queue.value = readl(TW_RESPONSE_QUEUE_REG_ADDR(tw_dev));
+ response_request_id = (unsigned char)response_queue.u.response_id;
+ if (request_id != response_request_id) {
+ TW_PRINTK(tw_dev->host, TW_DRIVER, 0x1e, "Found unexpected request id while polling for response");
+ goto out;
+ }
+ if (full_command_packet->command.newcommand.command.opcode == TW_OP_EXECUTE_SCSI) {
+ if (full_command_packet->command.newcommand.status != 0) {
+ /* bad response */
+ twa_fill_sense(tw_dev, request_id, 0, 0);
+ goto out;
+ }
+ found = 1;
+ } else {
+ if (full_command_packet->command.oldcommand.status != 0) {
+ /* bad response */
+ twa_fill_sense(tw_dev, request_id, 0, 0);
+ goto out;
+ }
+ found = 1;
+ }
+ }
+
+ if (found)
+ retval = 0;
+out:
+ return retval;
+} /* End twa_poll_response() */
+
+/* This function will poll the status register for a flag */
+static int twa_poll_status(TW_Device_Extension *tw_dev, u32 flag, int seconds)
+{
+ u32 status_reg_value;
+ unsigned long before;
+ int retval = 1;
+
+ status_reg_value = readl(TW_STATUS_REG_ADDR(tw_dev));
+ before = jiffies;
+
+ if (twa_check_bits(status_reg_value))
+ twa_decode_bits(tw_dev, status_reg_value);
+
+ while ((status_reg_value & flag) != flag) {
+ status_reg_value = readl(TW_STATUS_REG_ADDR(tw_dev));
+
+ if (twa_check_bits(status_reg_value))
+ twa_decode_bits(tw_dev, status_reg_value);
+
+ if (time_after(jiffies, before + HZ * seconds))
+ goto out;
+
+ mdelay(5);
+ }
+ retval = 0;
+out:
+ return retval;
+} /* End twa_poll_status() */
+
+/* This function will poll the status register for disappearance of a flag */
+static int twa_poll_status_gone(TW_Device_Extension *tw_dev, u32 flag, int seconds)
+{
+ u32 status_reg_value;
+ unsigned long before;
+ int retval = 1;
+
+ status_reg_value = readl(TW_STATUS_REG_ADDR(tw_dev));
+ before = jiffies;
+
+ if (twa_check_bits(status_reg_value))
+ twa_decode_bits(tw_dev, status_reg_value);
+
+ while ((status_reg_value & flag) != 0) {
+ status_reg_value = readl(TW_STATUS_REG_ADDR(tw_dev));
+ if (twa_check_bits(status_reg_value))
+ twa_decode_bits(tw_dev, status_reg_value);
+
+ if (time_after(jiffies, before + HZ * seconds))
+ goto out;
+
+ mdelay(5);
+ }
+ retval = 0;
+out:
+ return retval;
+} /* End twa_poll_status_gone() */
+
+/* This function will attempt to post a command packet to the board */
+static int twa_post_command_packet(TW_Device_Extension *tw_dev, int request_id)
+{
+ u32 status_reg_value;
+ unsigned long command_que_value;
+ int retval = 1;
+
+ command_que_value = tw_dev->command_packet_phys[request_id];
+ status_reg_value = readl(TW_STATUS_REG_ADDR(tw_dev));
+
+ if (twa_check_bits(status_reg_value))
+ twa_decode_bits(tw_dev, status_reg_value);
+
+ if (((tw_dev->pending_request_count > 0) && (tw_dev->state[request_id] != TW_S_PENDING)) || (status_reg_value & TW_STATUS_COMMAND_QUEUE_FULL)) {
+ /* Couldn't post the command packet, so we do it later */
+ if (tw_dev->state[request_id] != TW_S_PENDING) {
+ tw_dev->state[request_id] = TW_S_PENDING;
+ tw_dev->pending_request_count++;
+ if (tw_dev->pending_request_count > tw_dev->max_pending_request_count) {
+ tw_dev->max_pending_request_count = tw_dev->pending_request_count;
+ }
+ tw_dev->pending_queue[tw_dev->pending_tail] = request_id;
+ tw_dev->pending_tail = (tw_dev->pending_tail + 1) % TW_Q_LENGTH;
+ }
+ TW_UNMASK_COMMAND_INTERRUPT(tw_dev);
+ goto out;
+ } else {
+ /* We successfully posted the command packet */
+#if BITS_PER_LONG > 32
+ writeq(TW_COMMAND_OFFSET + command_que_value, TW_COMMAND_QUEUE_REG_ADDR(tw_dev));
+#else
+ writel(TW_COMMAND_OFFSET + command_que_value, TW_COMMAND_QUEUE_REG_ADDR(tw_dev));
+#endif
+ tw_dev->state[request_id] = TW_S_POSTED;
+ tw_dev->posted_request_count++;
+ if (tw_dev->posted_request_count > tw_dev->max_posted_request_count) {
+ tw_dev->max_posted_request_count = tw_dev->posted_request_count;
+ }
+ }
+ retval = 0;
+out:
+ return retval;
+} /* End twa_post_command_packet() */
+
+/* This function will reset a device extension */
+static int twa_reset_device_extension(TW_Device_Extension *tw_dev)
+{
+ int i = 0;
+ int retval = 1;
+
+ /* Abort all requests that are in progress */
+ for (i = 0; i < TW_Q_LENGTH; i++) {
+ if ((tw_dev->state[i] != TW_S_FINISHED) &&
+ (tw_dev->state[i] != TW_S_INITIAL) &&
+ (tw_dev->state[i] != TW_S_COMPLETED)) {
+ if (tw_dev->srb[i]) {
+ tw_dev->srb[i]->result = (DID_RESET << 16);
+ tw_dev->srb[i]->scsi_done(tw_dev->srb[i]);
+ twa_unmap_scsi_data(tw_dev, i);
+ }
+ }
+ }
+
+ /* Reset queues and counts */
+ for (i = 0; i < TW_Q_LENGTH; i++) {
+ tw_dev->free_queue[i] = i;
+ tw_dev->state[i] = TW_S_INITIAL;
+ }
+ tw_dev->free_head = TW_Q_START;
+ tw_dev->free_tail = TW_Q_START;
+ tw_dev->posted_request_count = 0;
+ tw_dev->pending_request_count = 0;
+ tw_dev->pending_head = TW_Q_START;
+ tw_dev->pending_tail = TW_Q_START;
+ tw_dev->reset_print = 0;
+ tw_dev->chrdev_request_id = TW_IOCTL_CHRDEV_FREE;
+ tw_dev->flags = 0;
+
+ TW_DISABLE_INTERRUPTS(tw_dev);
+
+ if (twa_reset_sequence(tw_dev, 1))
+ goto out;
+
+ TW_ENABLE_AND_CLEAR_INTERRUPTS(tw_dev);
+
+ retval = 0;
+out:
+ return retval;
+} /* End twa_reset_device_extension() */
+
+/* This function will reset a controller */
+static int twa_reset_sequence(TW_Device_Extension *tw_dev, int soft_reset)
+{
+ int tries = 0, retval = 1, flashed = 0, do_soft_reset = soft_reset;
+
+ while (tries < TW_MAX_RESET_TRIES) {
+ if (do_soft_reset)
+ TW_SOFT_RESET(tw_dev);
+
+ /* Make sure controller is in a good state */
+ if (twa_poll_status(tw_dev, TW_STATUS_MICROCONTROLLER_READY | (do_soft_reset == 1 ? TW_STATUS_ATTENTION_INTERRUPT : 0), 30)) {
+ TW_PRINTK(tw_dev->host, TW_DRIVER, 0x1f, "Microcontroller not ready during reset sequence");
+ do_soft_reset = 1;
+ tries++;
+ continue;
+ }
+
+ /* Empty response queue */
+ if (twa_empty_response_queue(tw_dev)) {
+ TW_PRINTK(tw_dev->host, TW_DRIVER, 0x20, "Response queue empty failed during reset sequence");
+ do_soft_reset = 1;
+ tries++;
+ continue;
+ }
+
+ flashed = 0;
+
+ /* Check for compatibility/flash */
+ if (twa_check_srl(tw_dev, &flashed)) {
+ TW_PRINTK(tw_dev->host, TW_DRIVER, 0x21, "Compatibility check failed during reset sequence");
+ do_soft_reset = 1;
+ tries++;
+ continue;
+ } else {
+ if (flashed) {
+ tries++;
+ continue;
+ }
+ }
+
+ /* Drain the AEN queue */
+ if (twa_aen_drain_queue(tw_dev, soft_reset)) {
+ TW_PRINTK(tw_dev->host, TW_DRIVER, 0x22, "AEN drain failed during reset sequence");
+ do_soft_reset = 1;
+ tries++;
+ continue;
+ }
+
+ /* If we got here, controller is in a good state */
+ retval = 0;
+ goto out;
+ }
+out:
+ return retval;
+} /* End twa_reset_sequence() */
+
+/* This funciton returns unit geometry in cylinders/heads/sectors */
+static int twa_scsi_biosparam(struct scsi_device *sdev, struct block_device *bdev, sector_t capacity, int geom[])
+{
+ int heads, sectors, cylinders;
+ TW_Device_Extension *tw_dev;
+
+ tw_dev = (TW_Device_Extension *)sdev->host->hostdata;
+
+ heads = 64;
+ sectors = 32;
+ cylinders = (unsigned long)capacity / (heads * sectors);
+
+ if (capacity >= 0x200000) {
+ heads = 255;
+ sectors = 63;
+ cylinders = (unsigned long)capacity / (heads * sectors);
+ }
+
+ geom[0] = heads;
+ geom[1] = sectors;
+ geom[2] = cylinders;
+
+ return 0;
+} /* End twa_scsi_biosparam() */
+
+/* This function will find and initialize all cards */
+static int twa_scsi_detect(Scsi_Host_Template *tw_host)
+{
+ int numcards = 0, i;
+ struct Scsi_Host *host = NULL;
+ TW_Device_Extension *tw_dev, *tw_dev2;
+ struct pci_dev *tw_pci_dev=NULL;
+ u32 mem_addr, mem_len;
+ u16 device[TW_NUMDEVICES] = { TW_DEVICE_ID_9000 };
+
+ printk(KERN_WARNING "3ware 9000 Storage Controller device driver for Linux v%s.\n", twa_driver_version);
+
+ for (i = 0; i < TW_NUMDEVICES; i++) {
+ while ((tw_pci_dev = pci_find_device(TW_VENDOR_ID, device[i], tw_pci_dev))) {
+ if (pci_enable_device(tw_pci_dev))
+ continue;
+
+ if (pci_set_dma_mask(tw_pci_dev, TW_DMA_MASK)) {
+ TW_PRINTK(host, TW_DRIVER, 0x23, "Failed to set dma mask");
+ continue;
+ }
+
+ /* Prepare temporary device extension */
+ tw_dev=(TW_Device_Extension *)kmalloc(sizeof(TW_Device_Extension), GFP_ATOMIC);
+ if (!tw_dev) {
+ TW_PRINTK(host, TW_DRIVER, 0x24, "Failed to allocate memory for device extension");
+ continue;
+ }
+ memset(tw_dev, 0, sizeof(TW_Device_Extension));
+
+ /* Save pci_dev struct to device extension */
+ tw_dev->tw_pci_dev = tw_pci_dev;
+
+ if (twa_initialize_device_extension(tw_dev)) {
+ TW_PRINTK(tw_dev->host, TW_DRIVER, 0x25, "Failed to initialize device extension");
+ twa_free_device_extension(tw_dev);
+ kfree(tw_dev);
+ continue;
+ }
+
+ mem_addr = pci_resource_start(tw_pci_dev, 1);
+ mem_len = pci_resource_len(tw_pci_dev, 1);
+
+ /* Make sure that mem region isn't already taken */
+ if (check_mem_region(mem_addr, mem_len)) {
+ TW_PRINTK(tw_dev->host, TW_DRIVER, 0x26, "Failed to get mem region");
+ twa_free_device_extension(tw_dev);
+ kfree(tw_dev);
+ continue;
+ }
+
+ /* Reserve the mem address space */
+ request_mem_region(mem_addr, mem_len, TW_DEVICE_NAME);
+
+ /* Save base address */
+ tw_dev->base_addr = ioremap(mem_addr, PAGE_SIZE);
+
+ /* Disable interrupts on the card */
+ TW_DISABLE_INTERRUPTS(tw_dev);
+
+ /* Initialize the card */
+ if (twa_reset_sequence(tw_dev, 0)) {
+ release_mem_region(mem_addr, mem_len);
+ twa_free_device_extension(tw_dev);
+ kfree(tw_dev);
+ continue;
+ }
+
+ /* Set card status as online */
+ tw_dev->online = 1;
+
+ /* Register the card with the kernel SCSI layer */
+ host = scsi_register(tw_host, sizeof(TW_Device_Extension));
+ if (!host) {
+ TW_PRINTK(tw_dev->host, TW_DRIVER, 0x27, "scsi register failed");
+ release_mem_region(mem_addr, mem_len);
+ twa_free_device_extension(tw_dev);
+ kfree(tw_dev);
+ continue;
+ }
+
+ /* Set max target id's */
+ host->max_id = TW_MAX_UNITS;
+
+ /* Set max cdb size in bytes */
+ host->max_cmd_len = TW_MAX_CDB_LEN;
+
+ scsi_set_device(host, &tw_pci_dev->dev);
+
+ printk(KERN_WARNING "3w-9xxx: scsi%d: Found a 3ware 9000 Storage Controller at 0x%x, IRQ: %d.\n", host->host_no, mem_addr, tw_pci_dev->irq);
+ printk(KERN_WARNING "3w-9xxx: scsi%d: Firmware %s, BIOS %s, Ports: %d.\n", host->host_no, (char *)twa_get_param(tw_dev, 0, TW_VERSION_TABLE, TW_PARAM_FWVER, TW_PARAM_FWVER_LENGTH), (char *)twa_get_param(tw_dev, 1, TW_VERSION_TABLE, TW_PARAM_BIOSVER, TW_PARAM_BIOSVER_LENGTH), *(int *)twa_get_param(tw_dev, 2, TW_INFORMATION_TABLE, TW_PARAM_PORTCOUNT, TW_PARAM_PORTCOUNT_LENGTH));
+ if (host->hostdata) {
+ tw_dev2 = (TW_Device_Extension *)host->hostdata;
+ memcpy(tw_dev2, tw_dev, sizeof(TW_Device_Extension));
+ /* Need to init the sem/wqueue after the copy */
+ init_MUTEX(&tw_dev2->ioctl_sem);
+ init_waitqueue_head(&tw_dev2->ioctl_wqueue);
+
+ twa_device_extension_list[twa_device_extension_count] = tw_dev2;
+ numcards++;
+ twa_device_extension_count = numcards;
+ tw_dev2->host = host;
+ } else {
+ TW_PRINTK(tw_dev->host, TW_DRIVER, 0x28, "Bad scsi host data");
+ scsi_unregister(host);
+ release_mem_region(mem_addr, mem_len);
+ twa_free_device_extension(tw_dev);
+ kfree(tw_dev);
+ continue;
+ }
+
+ /* Now setup the interrupt handler */
+ if (twa_setup_irq(tw_dev2)) {
+ scsi_unregister(host);
+ release_mem_region(mem_addr, mem_len);
+ twa_free_device_extension(tw_dev);
+ kfree(tw_dev);
+ numcards--;
+ continue;
+ }
+
+ /* Re-enable interrupts on the card */
+ TW_ENABLE_AND_CLEAR_INTERRUPTS(tw_dev2);
+
+ /* Free the temporary device extension */
+ if (tw_dev)
+ kfree(tw_dev);
+ }
+ }
+
+ if (numcards == 0) {
+ printk(KERN_WARNING "3w-9xxx: No cards successfully initialized.\n");
+ } else {
+ register_reboot_notifier(&twa_notifier);
+ if ((twa_major = register_chrdev (0, "twa", &twa_fops)) < 0)
+ TW_PRINTK(host, TW_DRIVER, 0x29, "Failed to register character device");
+ }
+ return numcards;
+} /* End twa_scsi_detect() */
+
+/* This is the new scsi eh abort function */
+static int twa_scsi_eh_abort(Scsi_Cmnd *SCpnt)
+{
+ int i;
+ TW_Device_Extension *tw_dev = NULL;
+ int retval = FAILED;
+
+ tw_dev = (TW_Device_Extension *)SCpnt->device->host->hostdata;
+
+ tw_dev->num_aborts++;
+
+ /* If we find any IO's in process, we have to reset the card */
+ for (i = 0; i < TW_Q_LENGTH; i++) {
+ if ((tw_dev->state[i] != TW_S_FINISHED) && (tw_dev->state[i] != TW_S_INITIAL)) {
+ printk(KERN_WARNING "3w-9xxx: scsi%d: WARNING: (0x%02X:0x%04X): Unit #%d: Command (0x%x) timed out, resetting card.\n", tw_dev->host->host_no, TW_DRIVER, 0x2c, SCpnt->device->id, SCpnt->cmnd[0]);
+ if (twa_reset_device_extension(tw_dev)) {
+ TW_PRINTK(tw_dev->host, TW_DRIVER, 0x2a, "Controller reset failed during scsi abort");
+ goto out;
+ }
+ break;
+ }
+ }
+ retval = SUCCESS;
+out:
+ return retval;
+} /* End twa_scsi_eh_abort() */
+
+/* This is the new scsi eh reset function */
+static int twa_scsi_eh_reset(Scsi_Cmnd *SCpnt)
+{
+ TW_Device_Extension *tw_dev = NULL;
+ int retval = FAILED;
+
+ tw_dev = (TW_Device_Extension *)SCpnt->device->host->hostdata;
+
+ tw_dev->num_resets++;
+
+ printk(KERN_WARNING "3w-9xxx: scsi%d: SCSI host reset started.\n", tw_dev->host->host_no);
+
+ /* Now reset the card and some of the device extension data */
+ if (twa_reset_device_extension(tw_dev)) {
+ TW_PRINTK(tw_dev->host, TW_DRIVER, 0x2b, "Controller reset failed during scsi host reset");
+ goto out;
+ }
+ printk(KERN_WARNING "3w-9xxx: scsi%d: SCSI host reset succeeded.\n", tw_dev->host->host_no);
+ retval = SUCCESS;
+out:
+ return retval;
+} /* End twa_scsi_eh_reset() */
+
+/* This function handles input and output from /proc/scsi/3w-9xxx/x */
+static int twa_scsi_proc_info(struct Scsi_Host *shost, char *buffer, char **start, off_t offset, int length, int inout)
+{
+ TW_Device_Extension *tw_dev = NULL;
+ TW_Info info;
+ unsigned int i;
+ int retval = -EINVAL;
+
+ /* Find the correct device extension */
+ for (i = 0; i < twa_device_extension_count; i++)
+ if (twa_device_extension_list[i]->host->host_no == shost->host_no)
+ tw_dev = twa_device_extension_list[i];
+ if (!tw_dev) {
+ TW_PRINTK(tw_dev->host, TW_DRIVER, 0x2c, "No device extension for proc operation");
+ goto out;
+ }
+
+ info.buffer = buffer;
+ info.length = length;
+ info.offset = offset;
+ info.position = 0;
+
+ if (!inout) {
+ /* Read */
+ if (start) {
+ *start = buffer;
+ }
+ twa_copy_info(&info, "scsi%d: 3ware 9000 Storage Controller\n", shost->host_no);
+ twa_copy_info(&info, "Driver version: %s\n", twa_driver_version);
+ twa_copy_info(&info, "Current commands posted: %4d\n", tw_dev->posted_request_count);
+ twa_copy_info(&info, "Max commands posted: %4d\n", tw_dev->max_posted_request_count);
+ twa_copy_info(&info, "Current pending commands: %4d\n", tw_dev->pending_request_count);
+ twa_copy_info(&info, "Max pending commands: %4d\n", tw_dev->max_pending_request_count);
+ twa_copy_info(&info, "Last sgl length: %4d\n", tw_dev->sgl_entries);
+ twa_copy_info(&info, "Max sgl length: %4d\n", tw_dev->max_sgl_entries);
+ twa_copy_info(&info, "Last sector count: %4d\n", tw_dev->sector_count);
+ twa_copy_info(&info, "Max sector count: %4d\n", tw_dev->max_sector_count);
+ twa_copy_info(&info, "SCSI Host Resets: %4d\n", tw_dev->num_resets);
+ twa_copy_info(&info, "SCSI Aborts/Timeouts: %4d\n", tw_dev->num_aborts);
+ twa_copy_info(&info, "AEN's: %4d\n", tw_dev->aen_count);
+ }
+ if (info.position > info.offset) {
+ retval = info.position - info.offset;
+ } else {
+ retval = 0;
+ }
+out:
+ return retval;
+} /* End twa_scsi_proc_info() */
+
+/* This is the main scsi queue function to handle scsi opcodes */
+static int twa_scsi_queue(Scsi_Cmnd *SCpnt, void (*done)(Scsi_Cmnd *))
+{
+ int request_id;
+ TW_Device_Extension *tw_dev = (TW_Device_Extension *)SCpnt->device->host->hostdata;
+
+ /* Skip lun and channel probes */
+ if ((SCpnt->device->lun != 0) || (SCpnt->device->channel != 0)) {
+ SCpnt->result = (DID_BAD_TARGET << 16);
+ done(SCpnt);
+ goto out;
+ }
+
+ /* Save done function into Scsi_Cmnd struct */
+ SCpnt->scsi_done = done;
+
+ /* Get a free request id */
+ twa_get_request_id(tw_dev, &request_id);
+
+ /* Save the scsi command for use by the ISR */
+ tw_dev->srb[request_id] = SCpnt;
+
+ /* Initialize phase to zero */
+ SCpnt->SCp.phase = 0;
+
+ if (twa_scsiop_execute_scsi(tw_dev, request_id, NULL, 0, NULL)) {
+ tw_dev->state[request_id] = TW_S_COMPLETED;
+ twa_free_request_id(tw_dev, request_id);
+ SCpnt->result = (DID_ERROR << 16);
+ done(SCpnt);
+ }
+out:
+ return 0;
+} /* End twa_scsi_queue() */
+
+/* This function will release the resources on an rmmod call */
+static int twa_scsi_release(struct Scsi_Host *tw_host)
+{
+ TW_Device_Extension *tw_dev = (TW_Device_Extension *)tw_host->hostdata;
+
+ twa_halt(0, 0, 0);
+
+ /* Free up the mem region */
+ release_mem_region(pci_resource_start(tw_dev->tw_pci_dev, 1), pci_resource_len(tw_dev->tw_pci_dev, 1));
+
+ /* Free up the IRQ */
+ free_irq(tw_dev->tw_pci_dev->irq, tw_dev);
+
+ /* Unregister character device */
+ if (twa_major >= 0) {
+ unregister_chrdev(twa_major, "twa");
+ twa_major = -1;
+ }
+
+ /* Free up device extension resources */
+ twa_free_device_extension(tw_dev);
+
+ /* Tell kernel scsi-layer we are gone */
+ scsi_unregister(tw_host);
+
+ return 0;
+} /* End twa_scsi_release() */
+
+/* This function hands scsi cdb's to the firmware */
+static int twa_scsiop_execute_scsi(TW_Device_Extension *tw_dev, int request_id, char *cdb, int use_sg, TW_SG_Apache *sglistarg)
+{
+ TW_Command_Full *full_command_packet;
+ TW_Command_Apache *command_packet;
+ u32 num_sectors = 0x0;
+ int i, sg_count;
+ Scsi_Cmnd *srb = NULL;
+ struct scatterlist *sglist = NULL;
+ u32 buffaddr = 0x0;
+ int retval = 1;
+
+ if (tw_dev->srb[request_id]) {
+ if (tw_dev->srb[request_id]->request_buffer) {
+ sglist = (struct scatterlist *)tw_dev->srb[request_id]->request_buffer;
+ }
+ srb = tw_dev->srb[request_id];
+ }
+
+ /* Initialize command packet */
+ full_command_packet = (TW_Command_Full *)tw_dev->command_packet_virt[request_id];
+ full_command_packet->header.header_desc.size_header = 128;
+ full_command_packet->header.status_block.error = 0;
+ full_command_packet->header.status_block.substatus_block.severity = 0;
+
+ command_packet = &full_command_packet->command.newcommand;
+ command_packet->status = 0;
+ command_packet->command.opcode = TW_OP_EXECUTE_SCSI;
+
+ /* We forced 16 byte cdb use earlier */
+ if (!cdb)
+ memcpy(command_packet->cdb, srb->cmnd, TW_MAX_CDB_LEN);
+ else
+ memcpy(command_packet->cdb, cdb, TW_MAX_CDB_LEN);
+
+ if (srb)
+ command_packet->unit = srb->device->id;
+ else
+ command_packet->unit = 0;
+
+ command_packet->request_id = request_id;
+ command_packet->sgl_offset = 16;
+
+ if (!sglistarg) {
+ /* Map sglist from scsi layer to cmd packet */
+ if (tw_dev->srb[request_id]->use_sg == 0) {
+ if (tw_dev->srb[request_id]->request_bufflen < TW_MIN_SGL_LENGTH) {
+ command_packet->sg_list[0].address = tw_dev->generic_buffer_phys[request_id];
+ command_packet->sg_list[0].length = TW_MIN_SGL_LENGTH;
+ } else {
+ buffaddr = twa_map_scsi_single_data(tw_dev, request_id);
+ if (buffaddr == 0)
+ goto out;
+
+ command_packet->sg_list[0].address = buffaddr;
+ command_packet->sg_list[0].length = tw_dev->srb[request_id]->request_bufflen;
+ }
+ command_packet->sgl_entries = 1;
+
+ if (command_packet->sg_list[0].address & TW_ALIGNMENT_9000_SGL) {
+ TW_PRINTK(tw_dev->host, TW_DRIVER, 0x2d, "Found unaligned address during execute scsi");
+ goto out;
+ }
+ }
+
+ if (tw_dev->srb[request_id]->use_sg > 0) {
+ sg_count = twa_map_scsi_sg_data(tw_dev, request_id);
+ if (sg_count == 0)
+ goto out;
+
+ for (i = 0; i < sg_count; i++) {
+ command_packet->sg_list[i].address = sg_dma_address(&sglist[i]);
+ command_packet->sg_list[i].length = sg_dma_len(&sglist[i]);
+ if (command_packet->sg_list[i].address & TW_ALIGNMENT_9000_SGL) {
+ TW_PRINTK(tw_dev->host, TW_DRIVER, 0x2e, "Found unaligned sgl address during execute scsi");
+ goto out;
+ }
+ }
+ command_packet->sgl_entries = tw_dev->srb[request_id]->use_sg;
+ }
+ } else {
+ /* Internal cdb post */
+ for (i = 0; i < use_sg; i++) {
+ command_packet->sg_list[i].address = sglistarg[i].address;
+ command_packet->sg_list[i].length = sglistarg[i].length;
+ if (command_packet->sg_list[i].address & TW_ALIGNMENT_9000_SGL) {
+ TW_PRINTK(tw_dev->host, TW_DRIVER, 0x2f, "Found unaligned sgl address during internal post");
+ goto out;
+ }
+ }
+ command_packet->sgl_entries = use_sg;
+ }
+
+ if (srb) {
+ if (srb->cmnd[0] == READ_6 || srb->cmnd[0] == WRITE_6)
+ num_sectors = (u32)srb->cmnd[4];
+
+ if (srb->cmnd[0] == READ_10 || srb->cmnd[0] == WRITE_10)
+ num_sectors = (u32)srb->cmnd[8] | ((u32)srb->cmnd[7] << 8);
+ }
+
+ /* Update sector statistic */
+ tw_dev->sector_count = num_sectors;
+ if (tw_dev->sector_count > tw_dev->max_sector_count)
+ tw_dev->max_sector_count = tw_dev->sector_count;
+
+ /* Update SG statistics */
+ if (srb) {
+ tw_dev->sgl_entries = tw_dev->srb[request_id]->use_sg;
+ if (tw_dev->sgl_entries > tw_dev->max_sgl_entries)
+ tw_dev->max_sgl_entries = tw_dev->sgl_entries;
+ }
+
+ /* Now post the command to the board */
+ twa_post_command_packet(tw_dev, request_id);
+ retval = 0;
+out:
+ return retval;
+} /* End twa_scsiop_execute_scsi() */
+
+/* This function completes an execute scsi operation */
+static void twa_scsiop_execute_scsi_complete(TW_Device_Extension *tw_dev, int request_id)
+{
+ /* Copy the response if too small */
+ if ((tw_dev->srb[request_id]->request_buffer) && (tw_dev->srb[request_id]->request_bufflen < TW_MIN_SGL_LENGTH)) {
+ memcpy(tw_dev->srb[request_id]->request_buffer, tw_dev->generic_buffer_virt[request_id], tw_dev->srb[request_id]->request_bufflen);
+ }
+} /* End twa_scsiop_execute_scsi_complete() */
+
+/* This function will setup the interrupt handler */
+static int twa_setup_irq(TW_Device_Extension *tw_dev)
+{
+ char *device = TW_DEVICE_NAME;
+ int retval = 1;
+
+ if (request_irq(tw_dev->tw_pci_dev->irq, twa_interrupt, SA_SHIRQ, device, tw_dev) < 0) {
+ TW_PRINTK(tw_dev->host, TW_DRIVER, 0x30, "Error requesting IRQ");
+ goto out;
+ }
+ retval = 0;
+out:
+ return retval;
+} /* End twa_setup_irq() */
+
+/* This function tells the controller to shut down */
+static void twa_shutdown_device(TW_Device_Extension *tw_dev)
+{
+ /* Disable interrupts */
+ TW_DISABLE_INTERRUPTS(tw_dev);
+
+ /* Tell the card we are shutting down */
+ if (twa_initconnection(tw_dev, 1, 0, 0, 0, 0, 0, NULL, NULL, NULL, NULL, NULL)) {
+ TW_PRINTK(tw_dev->host, TW_DRIVER, 0x31, "Connection shutdown failed");
+ } else {
+ printk(KERN_WARNING "3w-9xxx: Shutdown complete.\n");
+ }
+
+ /* Clear all interrupts just before exit */
+ TW_ENABLE_AND_CLEAR_INTERRUPTS(tw_dev);
+} /* End twa_shutdown_device() */
+
+/* This function will configure individual target parameters */
+static int twa_slave_configure(Scsi_Device *SDptr)
+{
+ int max_cmds;
+
+ if (cmds_per_lun) {
+ max_cmds = cmds_per_lun;
+ if (max_cmds > TW_MAX_CMDS_PER_LUN)
+ max_cmds = TW_MAX_CMDS_PER_LUN;
+ } else {
+ max_cmds = TW_MAX_CMDS_PER_LUN;
+ }
+ scsi_adjust_queue_depth(SDptr, MSG_ORDERED_TAG, max_cmds);
+
+ return 0;
+} /* End twa_slave_configure */
+
+/* This function will look up a string */
+static char *twa_string_lookup(twa_message_type *table, unsigned int code)
+{
+ int index;
+
+ for (index = 0; ((code != table[index].code) &&
+ (table[index].text != (char *)0)); index++);
+ return(table[index].text);
+} /* End twa_string_lookup() */
+
+/* This function will perform a pci-dma unmap */
+static void twa_unmap_scsi_data(TW_Device_Extension *tw_dev, int request_id)
+{
+ Scsi_Cmnd *cmd = tw_dev->srb[request_id];
+ int dma_dir = scsi_to_pci_dma_dir(cmd->sc_data_direction);
+ struct pci_dev *pdev = tw_dev->tw_pci_dev;
+
+ switch(cmd->SCp.phase) {
+ case 1:
+ pci_unmap_single(pdev, cmd->SCp.have_data_in, cmd->request_bufflen, dma_dir);
+ break;
+ case 2:
+ pci_unmap_sg(pdev, cmd->request_buffer, cmd->use_sg, dma_dir);
+ break;
+ }
+} /* End twa_unmap_scsi_data() */
+
+/* Now get things going */
+static Scsi_Host_Template driver_template = {
+ .proc_name = "3w-9xxx",
+ .proc_info = twa_scsi_proc_info,
+ .name = "3ware 9000 Storage Controller",
+ .detect = twa_scsi_detect,
+ .release = twa_scsi_release,
+ .queuecommand = twa_scsi_queue,
+ .eh_abort_handler = twa_scsi_eh_abort,
+ .eh_host_reset_handler = twa_scsi_eh_reset,
+ .bios_param = twa_scsi_biosparam,
+ .slave_configure = twa_slave_configure,
+ .can_queue = TW_Q_LENGTH-2,
+ .this_id = -1,
+ .sg_tablesize = TW_APACHE_MAX_SGL_LENGTH,
+ .max_sectors = TW_MAX_SECTORS,
+ .cmd_per_lun = TW_MAX_CMDS_PER_LUN,
+ .use_clustering = ENABLE_CLUSTERING,
+ .emulated = 1
+};
+#include "scsi_module.c"
+
^ permalink raw reply [flat|nested] 4+ messages in thread
* Re: [PATCH/RFC 1/2] 3ware 9000 SATA-RAID driver
2004-05-22 18:19 [PATCH/RFC 1/2] 3ware 9000 SATA-RAID driver Adam Radford
@ 2004-05-23 10:59 ` hch
0 siblings, 0 replies; 4+ messages in thread
From: hch @ 2004-05-23 10:59 UTC (permalink / raw)
To: Adam Radford; +Cc: linux-scsi
> diff -Naur linux-2.6.6-mm4/drivers/pci/pci.ids linux-2.6.6-mm5/drivers/pci/pci.ids
> --- linux-2.6.6-mm4/drivers/pci/pci.ids 2004-05-21 16:35:20.000000000 -0700
> +++ linux-2.6.6-mm5/drivers/pci/pci.ids 2004-05-21 15:38:58.000000000 -0700
> @@ -5751,7 +5751,7 @@
> 13c1 3ware Inc
> 1000 3ware ATA-RAID
> 1001 3ware 7000-series ATA-RAID
> - 1002 3ware ATA-RAID
> + 1002 3ware 9000-series ATA-RAID
> 13c2 Technotrend Systemtechnik GmbH
> 13c3 Janz Computer AG
> 13c4 Phase Metrics
I think this file is managed via pci-ids.sf.net, please submit there.
> +
> +#include <linux/module.h>
> +
> +MODULE_AUTHOR ("AMCC");
> +#ifdef CONFIG_SMP
> +MODULE_DESCRIPTION ("3ware 9000 Storage Controller Linux Driver (SMP)");
> +#else
> +MODULE_DESCRIPTION ("3ware 9000 Storage Controller Linux Driver");
> +#endif
> +MODULE_LICENSE("GPL");
the ifdef SMP is bogus, also please move the MODULE_ macros below all
includes.
> +
> +#include <linux/kernel.h>
> +#include <linux/pci.h>
> +#include <linux/time.h>
> +#include <linux/proc_fs.h>
> +#include <linux/sched.h>
> +#include <linux/ioport.h>
> +#include <linux/blkdev.h>
> +#include <linux/hdreg.h>
> +#include <linux/string.h>
> +#include <linux/delay.h>
> +#include <linux/smp.h>
shouldn't be needed with a modern driver.
> +#include <linux/reboot.h>
> +#include <linux/spinlock.h>
> +#include <linux/interrupt.h>
> +#include <linux/moduleparam.h>
> +
> +#include <asm/errno.h>
never use asm/errno.h but always linux/errno.h
> +#include "scsi.h"
> +#include "hosts.h"
please don't include these anymore, use the headers from include/scsi/*.h,
this also involves some reshuffling like getting rid of the obsolete Scsi_Foo
typedefs and using the DMA_* constants instead of SCSI_DATA_*
> +
> +#include "3w-9xxx.h"
> +
> +/* Notifier block to get a notify on system shutdown/halt/reboot */
> +static struct notifier_block twa_notifier = {
> + .notifier_call = twa_halt,
> + .next = NULL,
> + .priority = 0
> +};
please usethe shutdown method in the pci_driver instead, see the megaraid
driver for an example.
> +/* Globals */
> +char *twa_driver_version="2.26.00.008";
> +TW_Device_Extension *twa_device_extension_list[TW_MAX_SLOT];
after the changes I'll suggest below this will only be used in the ioctl
code anymore, and by doing the lookup in ->open there and stuffing a pointer
into file->private_data you'll only need this in a slow-path, so a linked
list will be enough.
> +unsigned int twa_device_extension_count = 0;
No need to initialize to 0, .bss will do it for you.
> +static int twa_major = -1;
> +extern struct timezone sys_tz;
> +static int cmds_per_lun;
please mark all symbols in the driver static.
> +/* This function handles open for the character device */
> +static int twa_chrdev_open(struct inode *inode, struct file *file)
> +{
> + unsigned int minor_number;
> + int retval = TW_IOCTL_ERROR_OS_ENODEV;
> +
> + minor_number = MINOR(inode->i_rdev);
2.6 has a nice iminor() helper for this.
> +/* This function handles close for the character device */
> +static int twa_chrdev_release(struct inode *inode, struct file *file)
> +{
> + return 0;
> +} /* End twa_chrdev_release() */
no need to implement ->release at all if it doesn't do anything.
> +#if BITS_PER_LONG > 32
> + /* Turn on 64-bit sgl support */
> + tw_initconnect->features |= 1;
> +#endif
Don't you also need this on 32bit arches with >32bit physical
addressing, e.g. x86 with PAE?
> +/* This funciton returns unit geometry in cylinders/heads/sectors */
> +static int twa_scsi_biosparam(struct scsi_device *sdev, struct block_device *bdev, sector_t capacity, int geom[])
> +{
> + int heads, sectors, cylinders;
> + TW_Device_Extension *tw_dev;
> +
> + tw_dev = (TW_Device_Extension *)sdev->host->hostdata;
> +
> + heads = 64;
> + sectors = 32;
> + cylinders = (unsigned long)capacity / (heads * sectors);
with CONFIG_LBD set capacity will be a 64bit type on 32bit arches,
so you must use sector_div for it.
> +/* This function will find and initialize all cards */
> +static int twa_scsi_detect(Scsi_Host_Template *tw_host)
> +{
> + int numcards = 0, i;
> + struct Scsi_Host *host = NULL;
> + TW_Device_Extension *tw_dev, *tw_dev2;
> + struct pci_dev *tw_pci_dev=NULL;
> + u32 mem_addr, mem_len;
> + u16 device[TW_NUMDEVICES] = { TW_DEVICE_ID_9000 };
> +
> + printk(KERN_WARNING "3ware 9000 Storage Controller device driver for Linux v%s.\n", twa_driver_version);
> +
> + for (i = 0; i < TW_NUMDEVICES; i++) {
> + while ((tw_pci_dev = pci_find_device(TW_VENDOR_ID, device[i], tw_pci_dev))) {
Please redo the probing useing a struct pci_driver and appropinquate
callbacks. See e.g. megaraid or qla1280 for details.
> +/* This function handles input and output from /proc/scsi/3w-9xxx/x */
> +static int twa_scsi_proc_info(struct Scsi_Host *shost, char *buffer, char **start, off_t offset, int length, int inout)
> +{
> + TW_Device_Extension *tw_dev = NULL;
> + TW_Info info;
> + unsigned int i;
> + int retval = -EINVAL;
> +
> + /* Find the correct device extension */
> + for (i = 0; i < twa_device_extension_count; i++)
> + if (twa_device_extension_list[i]->host->host_no == shost->host_no)
> + tw_dev = twa_device_extension_list[i];
You can simply use shost->private_data to find your device extension
these days. But we don't want ->proc_info in new drivers anyway, please
use sysfs attributes instead, see 53c700 or the qla2xxx driver for
examples.
> +/* This is the main scsi queue function to handle scsi opcodes */
> +static int twa_scsi_queue(Scsi_Cmnd *SCpnt, void (*done)(Scsi_Cmnd *))
> +{
> + int request_id;
> + TW_Device_Extension *tw_dev = (TW_Device_Extension *)SCpnt->device->host->hostdata;
> +
> + /* Skip lun and channel probes */
> + if ((SCpnt->device->lun != 0) || (SCpnt->device->channel != 0)) {
> + SCpnt->result = (DID_BAD_TARGET << 16);
> + done(SCpnt);
> + goto out;
> + }
If you set max_lun and max_channel right you should need this change.
> + /* Save done function into Scsi_Cmnd struct */
> + SCpnt->scsi_done = done;
> +
> + /* Get a free request id */
> + twa_get_request_id(tw_dev, &request_id);
> +
> + /* Save the scsi command for use by the ISR */
> + tw_dev->srb[request_id] = SCpnt;
> +
> + /* Initialize phase to zero */
> + SCpnt->SCp.phase = 0;
> +
> + if (twa_scsiop_execute_scsi(tw_dev, request_id, NULL, 0, NULL)) {
> + tw_dev->state[request_id] = TW_S_COMPLETED;
> + twa_free_request_id(tw_dev, request_id);
> + SCpnt->result = (DID_ERROR << 16);
> + done(SCpnt);
If twa_scsiop_execute_scsi returns a non-fatal error and the request
should be retired you should reurn one of the MLQUEUE*BUSY values
instead.
In addition to these code comments please make sure all lines are
wrapper after 80 characters.
^ permalink raw reply [flat|nested] 4+ messages in thread
* RE: [PATCH/RFC 1/2] 3ware 9000 SATA-RAID driver
@ 2004-05-25 17:46 Adam Radford
2004-05-25 19:19 ` Christoph Hellwig
0 siblings, 1 reply; 4+ messages in thread
From: Adam Radford @ 2004-05-25 17:46 UTC (permalink / raw)
To: hch; +Cc: linux-scsi
Hch,
Thanks for the feedback. I have already made most of the changes, including
converting the driver to pci_driver format. I will re-post the next version
soon. The only thing I cannot do is mark 'extern struct timezone sys_tz' as
static.
Thanks,
--
Adam Radford
Staff Software Engineer
AMCC
-----Original Message-----
From: linux-scsi-owner@vger.kernel.org
[mailto:linux-scsi-owner@vger.kernel.org]On Behalf Of hch@lst.de
Sent: Sunday, May 23, 2004 4:00 AM
To: Adam Radford
Cc: linux-scsi@vger.kernel.org
Subject: Re: [PATCH/RFC 1/2] 3ware 9000 SATA-RAID driver
> diff -Naur linux-2.6.6-mm4/drivers/pci/pci.ids linux-2.6.6-mm5/drivers/pci/pci.ids
> --- linux-2.6.6-mm4/drivers/pci/pci.ids 2004-05-21 16:35:20.000000000 -0700
> +++ linux-2.6.6-mm5/drivers/pci/pci.ids 2004-05-21 15:38:58.000000000 -0700
> @@ -5751,7 +5751,7 @@
> 13c1 3ware Inc
> 1000 3ware ATA-RAID
> 1001 3ware 7000-series ATA-RAID
> - 1002 3ware ATA-RAID
> + 1002 3ware 9000-series ATA-RAID
> 13c2 Technotrend Systemtechnik GmbH
> 13c3 Janz Computer AG
> 13c4 Phase Metrics
I think this file is managed via pci-ids.sf.net, please submit there.
> +
> +#include <linux/module.h>
> +
> +MODULE_AUTHOR ("AMCC");
> +#ifdef CONFIG_SMP
> +MODULE_DESCRIPTION ("3ware 9000 Storage Controller Linux Driver (SMP)");
> +#else
> +MODULE_DESCRIPTION ("3ware 9000 Storage Controller Linux Driver");
> +#endif
> +MODULE_LICENSE("GPL");
the ifdef SMP is bogus, also please move the MODULE_ macros below all
includes.
> +
> +#include <linux/kernel.h>
> +#include <linux/pci.h>
> +#include <linux/time.h>
> +#include <linux/proc_fs.h>
> +#include <linux/sched.h>
> +#include <linux/ioport.h>
> +#include <linux/blkdev.h>
> +#include <linux/hdreg.h>
> +#include <linux/string.h>
> +#include <linux/delay.h>
> +#include <linux/smp.h>
shouldn't be needed with a modern driver.
> +#include <linux/reboot.h>
> +#include <linux/spinlock.h>
> +#include <linux/interrupt.h>
> +#include <linux/moduleparam.h>
> +
> +#include <asm/errno.h>
never use asm/errno.h but always linux/errno.h
> +#include "scsi.h"
> +#include "hosts.h"
please don't include these anymore, use the headers from include/scsi/*.h,
this also involves some reshuffling like getting rid of the obsolete Scsi_Foo
typedefs and using the DMA_* constants instead of SCSI_DATA_*
> +
> +#include "3w-9xxx.h"
> +
> +/* Notifier block to get a notify on system shutdown/halt/reboot */
> +static struct notifier_block twa_notifier = {
> + .notifier_call = twa_halt,
> + .next = NULL,
> + .priority = 0
> +};
please usethe shutdown method in the pci_driver instead, see the megaraid
driver for an example.
> +/* Globals */
> +char *twa_driver_version="2.26.00.008";
> +TW_Device_Extension *twa_device_extension_list[TW_MAX_SLOT];
after the changes I'll suggest below this will only be used in the ioctl
code anymore, and by doing the lookup in ->open there and stuffing a pointer
into file->private_data you'll only need this in a slow-path, so a linked
list will be enough.
> +unsigned int twa_device_extension_count = 0;
No need to initialize to 0, .bss will do it for you.
> +static int twa_major = -1;
> +extern struct timezone sys_tz;
> +static int cmds_per_lun;
please mark all symbols in the driver static.
> +/* This function handles open for the character device */
> +static int twa_chrdev_open(struct inode *inode, struct file *file)
> +{
> + unsigned int minor_number;
> + int retval = TW_IOCTL_ERROR_OS_ENODEV;
> +
> + minor_number = MINOR(inode->i_rdev);
2.6 has a nice iminor() helper for this.
> +/* This function handles close for the character device */
> +static int twa_chrdev_release(struct inode *inode, struct file *file)
> +{
> + return 0;
> +} /* End twa_chrdev_release() */
no need to implement ->release at all if it doesn't do anything.
> +#if BITS_PER_LONG > 32
> + /* Turn on 64-bit sgl support */
> + tw_initconnect->features |= 1;
> +#endif
Don't you also need this on 32bit arches with >32bit physical
addressing, e.g. x86 with PAE?
> +/* This funciton returns unit geometry in cylinders/heads/sectors */
> +static int twa_scsi_biosparam(struct scsi_device *sdev, struct block_device *bdev, sector_t capacity, int geom[])
> +{
> + int heads, sectors, cylinders;
> + TW_Device_Extension *tw_dev;
> +
> + tw_dev = (TW_Device_Extension *)sdev->host->hostdata;
> +
> + heads = 64;
> + sectors = 32;
> + cylinders = (unsigned long)capacity / (heads * sectors);
with CONFIG_LBD set capacity will be a 64bit type on 32bit arches,
so you must use sector_div for it.
> +/* This function will find and initialize all cards */
> +static int twa_scsi_detect(Scsi_Host_Template *tw_host)
> +{
> + int numcards = 0, i;
> + struct Scsi_Host *host = NULL;
> + TW_Device_Extension *tw_dev, *tw_dev2;
> + struct pci_dev *tw_pci_dev=NULL;
> + u32 mem_addr, mem_len;
> + u16 device[TW_NUMDEVICES] = { TW_DEVICE_ID_9000 };
> +
> + printk(KERN_WARNING "3ware 9000 Storage Controller device driver for Linux v%s.\n", twa_driver_version);
> +
> + for (i = 0; i < TW_NUMDEVICES; i++) {
> + while ((tw_pci_dev = pci_find_device(TW_VENDOR_ID, device[i], tw_pci_dev))) {
Please redo the probing useing a struct pci_driver and appropinquate
callbacks. See e.g. megaraid or qla1280 for details.
> +/* This function handles input and output from /proc/scsi/3w-9xxx/x */
> +static int twa_scsi_proc_info(struct Scsi_Host *shost, char *buffer, char **start, off_t offset, int length, int inout)
> +{
> + TW_Device_Extension *tw_dev = NULL;
> + TW_Info info;
> + unsigned int i;
> + int retval = -EINVAL;
> +
> + /* Find the correct device extension */
> + for (i = 0; i < twa_device_extension_count; i++)
> + if (twa_device_extension_list[i]->host->host_no == shost->host_no)
> + tw_dev = twa_device_extension_list[i];
You can simply use shost->private_data to find your device extension
these days. But we don't want ->proc_info in new drivers anyway, please
use sysfs attributes instead, see 53c700 or the qla2xxx driver for
examples.
> +/* This is the main scsi queue function to handle scsi opcodes */
> +static int twa_scsi_queue(Scsi_Cmnd *SCpnt, void (*done)(Scsi_Cmnd *))
> +{
> + int request_id;
> + TW_Device_Extension *tw_dev = (TW_Device_Extension *)SCpnt->device->host->hostdata;
> +
> + /* Skip lun and channel probes */
> + if ((SCpnt->device->lun != 0) || (SCpnt->device->channel != 0)) {
> + SCpnt->result = (DID_BAD_TARGET << 16);
> + done(SCpnt);
> + goto out;
> + }
If you set max_lun and max_channel right you should need this change.
> + /* Save done function into Scsi_Cmnd struct */
> + SCpnt->scsi_done = done;
> +
> + /* Get a free request id */
> + twa_get_request_id(tw_dev, &request_id);
> +
> + /* Save the scsi command for use by the ISR */
> + tw_dev->srb[request_id] = SCpnt;
> +
> + /* Initialize phase to zero */
> + SCpnt->SCp.phase = 0;
> +
> + if (twa_scsiop_execute_scsi(tw_dev, request_id, NULL, 0, NULL)) {
> + tw_dev->state[request_id] = TW_S_COMPLETED;
> + twa_free_request_id(tw_dev, request_id);
> + SCpnt->result = (DID_ERROR << 16);
> + done(SCpnt);
If twa_scsiop_execute_scsi returns a non-fatal error and the request
should be retired you should reurn one of the MLQUEUE*BUSY values
instead.
In addition to these code comments please make sure all lines are
wrapper after 80 characters.
-
To unsubscribe from this list: send the line "unsubscribe linux-scsi" in
the body of a message to majordomo@vger.kernel.org
More majordomo info at http://vger.kernel.org/majordomo-info.html
^ permalink raw reply [flat|nested] 4+ messages in thread
* Re: [PATCH/RFC 1/2] 3ware 9000 SATA-RAID driver
2004-05-25 17:46 Adam Radford
@ 2004-05-25 19:19 ` Christoph Hellwig
0 siblings, 0 replies; 4+ messages in thread
From: Christoph Hellwig @ 2004-05-25 19:19 UTC (permalink / raw)
To: Adam Radford; +Cc: linux-scsi
On Tue, May 25, 2004 at 10:46:37AM -0700, Adam Radford wrote:
> Hch,
>
> Thanks for the feedback. I have already made most of the changes, including
> converting the driver to pci_driver format. I will re-post the next version
> soon. The only thing I cannot do is mark 'extern struct timezone sys_tz' as
> static.
Hmm, I wonder why you need that. But I'm too lazy to look at the old
patch again and will check when you post the next revision.
^ permalink raw reply [flat|nested] 4+ messages in thread
end of thread, other threads:[~2004-05-25 19:19 UTC | newest]
Thread overview: 4+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2004-05-22 18:19 [PATCH/RFC 1/2] 3ware 9000 SATA-RAID driver Adam Radford
2004-05-23 10:59 ` hch
-- strict thread matches above, loose matches on Subject: below --
2004-05-25 17:46 Adam Radford
2004-05-25 19:19 ` Christoph Hellwig
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox