aboutsummaryrefslogtreecommitdiff
path: root/drivers/scsi/3w-xxxx.c
diff options
context:
space:
mode:
authorLinus Torvalds <torvalds@ppc970.osdl.org>2005-04-16 15:20:36 -0700
committerLinus Torvalds <torvalds@ppc970.osdl.org>2005-04-16 15:20:36 -0700
commit1da177e4c3f41524e886b7f1b8a0c1fc7321cac2 (patch)
tree0bba044c4ce775e45a88a51686b5d9f90697ea9d /drivers/scsi/3w-xxxx.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/3w-xxxx.c')
-rw-r--r--drivers/scsi/3w-xxxx.c2478
1 files changed, 2478 insertions, 0 deletions
diff --git a/drivers/scsi/3w-xxxx.c b/drivers/scsi/3w-xxxx.c
new file mode 100644
index 00000000000..48f9ece1cbd
--- /dev/null
+++ b/drivers/scsi/3w-xxxx.c
@@ -0,0 +1,2478 @@
+/*
+ 3w-xxxx.c -- 3ware Storage Controller device driver for Linux.
+
+ Written By: Adam Radford <linuxraid@amcc.com>
+ Modifications By: Joel Jacobson <linux@3ware.com>
+ Arnaldo Carvalho de Melo <acme@conectiva.com.br>
+ Brad Strand <linux@3ware.com>
+
+ Copyright (C) 1999-2005 3ware Inc.
+
+ Kernel compatiblity By: Andre Hedrick <andre@suse.com>
+ Non-Copyright (C) 2000 Andre Hedrick <andre@suse.com>
+
+ Further tiny build fixes and trivial hoovering Alan Cox
+
+ 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
+
+ History
+ -------
+ 0.1.000 - Initial release.
+ 0.4.000 - Added support for Asynchronous Event Notification through
+ ioctls for 3DM.
+ 1.0.000 - Added DPO & FUA bit support for WRITE_10 & WRITE_6 cdb
+ to disable drive write-cache before writes.
+ 1.1.000 - Fixed performance bug with DPO & FUA not existing for WRITE_6.
+ 1.2.000 - Added support for clean shutdown notification/feature table.
+ 1.02.00.001 - Added support for full command packet posts through ioctls
+ for 3DM.
+ Bug fix so hot spare drives don't show up.
+ 1.02.00.002 - Fix bug with tw_setfeature() call that caused oops on some
+ systems.
+ 08/21/00 - release previously allocated resources on failure at
+ tw_allocate_memory (acme)
+ 1.02.00.003 - Fix tw_interrupt() to report error to scsi layer when
+ controller status is non-zero.
+ Added handling of request_sense opcode.
+ Fix possible null pointer dereference in
+ tw_reset_device_extension()
+ 1.02.00.004 - Add support for device id of 3ware 7000 series controllers.
+ Make tw_setfeature() call with interrupts disabled.
+ Register interrupt handler before enabling interrupts.
+ Clear attention interrupt before draining aen queue.
+ 1.02.00.005 - Allocate bounce buffers and custom queue depth for raid5 for
+ 6000 and 5000 series controllers.
+ Reduce polling mdelays causing problems on some systems.
+ Fix use_sg = 1 calculation bug.
+ Check for scsi_register returning NULL.
+ Add aen count to /proc/scsi/3w-xxxx.
+ Remove aen code unit masking in tw_aen_complete().
+ 1.02.00.006 - Remove unit from printk in tw_scsi_eh_abort(), causing
+ possible oops.
+ Fix possible null pointer dereference in tw_scsi_queue()
+ if done function pointer was invalid.
+ 1.02.00.007 - Fix possible null pointer dereferences in tw_ioctl().
+ Remove check for invalid done function pointer from
+ tw_scsi_queue().
+ 1.02.00.008 - Set max sectors per io to TW_MAX_SECTORS in tw_findcards().
+ Add tw_decode_error() for printing readable error messages.
+ Print some useful information on certain aen codes.
+ Add tw_decode_bits() for interpreting status register output.
+ Make scsi_set_pci_device() for kernels >= 2.4.4
+ Fix bug where aen's could be lost before a reset.
+ Re-add spinlocks in tw_scsi_detect().
+ Fix possible null pointer dereference in tw_aen_drain_queue()
+ during initialization.
+ Clear pci parity errors during initialization and during io.
+ 1.02.00.009 - Remove redundant increment in tw_state_request_start().
+ Add ioctl support for direct ATA command passthru.
+ Add entire aen code string list.
+ 1.02.00.010 - Cleanup queueing code, fix jbod thoughput.
+ Fix get_param for specific units.
+ 1.02.00.011 - Fix bug in tw_aen_complete() where aen's could be lost.
+ Fix tw_aen_drain_queue() to display useful info at init.
+ Set tw_host->max_id for 12 port cards.
+ Add ioctl support for raw command packet post from userspace
+ with sglist fragments (parameter and io).
+ 1.02.00.012 - Fix read capacity to under report by 1 sector to fix get
+ last sector ioctl.
+ 1.02.00.013 - Fix bug where more AEN codes weren't coming out during
+ driver initialization.
+ Improved handling of PCI aborts.
+ 1.02.00.014 - Fix bug in tw_findcards() where AEN code could be lost.
+ Increase timeout in tw_aen_drain_queue() to 30 seconds.
+ 1.02.00.015 - Re-write raw command post with data ioctl method.
+ Remove raid5 bounce buffers for raid5 for 6XXX for kernel 2.5
+ Add tw_map/unmap_scsi_sg/single_data() for kernel 2.5
+ Replace io_request_lock with host_lock for kernel 2.5
+ Set max_cmd_len to 16 for 3dm for kernel 2.5
+ 1.02.00.016 - Set host->max_sectors back up to 256.
+ 1.02.00.017 - Modified pci parity error handling/clearing from config space
+ during initialization.
+ 1.02.00.018 - Better handling of request sense opcode and sense information
+ for failed commands. Add tw_decode_sense().
+ Replace all mdelay()'s with scsi_sleep().
+ 1.02.00.019 - Revert mdelay's and scsi_sleep's, this caused problems on
+ some SMP systems.
+ 1.02.00.020 - Add pci_set_dma_mask(), rewrite kmalloc()/virt_to_bus() to
+ pci_alloc/free_consistent().
+ Better alignment checking in tw_allocate_memory().
+ Cleanup tw_initialize_device_extension().
+ 1.02.00.021 - Bump cmd_per_lun in SHT to 255 for better jbod performance.
+ Improve handling of errors in tw_interrupt().
+ Add handling/clearing of controller queue error.
+ Empty stale responses before draining aen queue.
+ Fix tw_scsi_eh_abort() to not reset on every io abort.
+ Set can_queue in SHT to 255 to prevent hang from AEN.
+ 1.02.00.022 - Fix possible null pointer dereference in tw_scsi_release().
+ 1.02.00.023 - Fix bug in tw_aen_drain_queue() where unit # was always zero.
+ 1.02.00.024 - Add severity levels to AEN strings.
+ 1.02.00.025 - Fix command interrupt spurious error messages.
+ Fix bug in raw command post with data ioctl method.
+ Fix bug where rollcall sometimes failed with cable errors.
+ Print unit # on all command timeouts.
+ 1.02.00.026 - Fix possible infinite retry bug with power glitch induced
+ drive timeouts.
+ Cleanup some AEN severity levels.
+ 1.02.00.027 - Add drive not supported AEN code for SATA controllers.
+ Remove spurious unknown ioctl error message.
+ 1.02.00.028 - Fix bug where multiple controllers with no units were the
+ same card number.
+ Fix bug where cards were being shut down more than once.
+ 1.02.00.029 - Add missing pci_free_consistent() in tw_allocate_memory().
+ Replace pci_map_single() with pci_map_page() for highmem.
+ Check for tw_setfeature() failure.
+ 1.02.00.030 - Make driver 64-bit clean.
+ 1.02.00.031 - Cleanup polling timeouts/routines in several places.
+ Add support for mode sense opcode.
+ Add support for cache mode page.
+ Add support for synchronize cache opcode.
+ 1.02.00.032 - Fix small multicard rollcall bug.
+ Make driver stay loaded with no units for hot add/swap.
+ Add support for "twe" character device for ioctls.
+ Clean up request_id queueing code.
+ Fix tw_scsi_queue() spinlocks.
+ 1.02.00.033 - Fix tw_aen_complete() to not queue 'queue empty' AEN's.
+ Initialize queues correctly when loading with no valid units.
+ 1.02.00.034 - Fix tw_decode_bits() to handle multiple errors.
+ Add support for user configurable cmd_per_lun.
+ Add support for sht->slave_configure().
+ 1.02.00.035 - Improve tw_allocate_memory() memory allocation.
+ Fix tw_chrdev_ioctl() to sleep correctly.
+ 1.02.00.036 - Increase character ioctl timeout to 60 seconds.
+ 1.02.00.037 - Fix tw_ioctl() to handle all non-data ATA passthru cmds
+ for 'smartmontools' support.
+ 1.26.00.038 - Roll driver minor version to 26 to denote kernel 2.6.
+ Add support for cmds_per_lun module parameter.
+ 1.26.00.039 - Fix bug in tw_chrdev_ioctl() polling code.
+ Fix data_buffer_length usage in tw_chrdev_ioctl().
+ Update contact information.
+ 1.26.02.000 - Convert driver to pci_driver format.
+ 1.26.02.001 - Increase max ioctl buffer size to 512 sectors.
+ Make tw_scsi_queue() return 0 for 'Unknown scsi opcode'.
+ Fix tw_remove() to free irq handler/unregister_chrdev()
+ before shutting down card.
+ Change to new 'change_queue_depth' api.
+ Fix 'handled=1' ISR usage, remove bogus IRQ check.
+*/
+
+#include <linux/module.h>
+#include <linux/reboot.h>
+#include <linux/spinlock.h>
+#include <linux/interrupt.h>
+#include <linux/moduleparam.h>
+#include <linux/errno.h>
+#include <linux/types.h>
+#include <linux/delay.h>
+#include <linux/pci.h>
+#include <linux/time.h>
+#include <asm/io.h>
+#include <asm/irq.h>
+#include <asm/uaccess.h>
+#include <scsi/scsi.h>
+#include <scsi/scsi_host.h>
+#include <scsi/scsi_tcq.h>
+#include <scsi/scsi_cmnd.h>
+#include "3w-xxxx.h"
+
+/* Globals */
+#define TW_DRIVER_VERSION "1.26.02.001"
+static TW_Device_Extension *tw_device_extension_list[TW_MAX_SLOT];
+static int tw_device_extension_count = 0;
+static int twe_major = -1;
+
+/* Module parameters */
+MODULE_AUTHOR("AMCC");
+MODULE_DESCRIPTION("3ware Storage Controller Linux Driver");
+MODULE_LICENSE("GPL");
+MODULE_VERSION(TW_DRIVER_VERSION);
+
+/* Function prototypes */
+static int tw_reset_device_extension(TW_Device_Extension *tw_dev, int ioctl_reset);
+
+/* Functions */
+
+/* This function will check the status register for unexpected bits */
+static int tw_check_bits(u32 status_reg_value)
+{
+ if ((status_reg_value & TW_STATUS_EXPECTED_BITS) != TW_STATUS_EXPECTED_BITS) {
+ dprintk(KERN_WARNING "3w-xxxx: tw_check_bits(): No expected bits (0x%x).\n", status_reg_value);
+ return 1;
+ }
+ if ((status_reg_value & TW_STATUS_UNEXPECTED_BITS) != 0) {
+ dprintk(KERN_WARNING "3w-xxxx: tw_check_bits(): Found unexpected bits (0x%x).\n", status_reg_value);
+ return 1;
+ }
+
+ return 0;
+} /* End tw_check_bits() */
+
+/* This function will print readable messages from status register errors */
+static int tw_decode_bits(TW_Device_Extension *tw_dev, u32 status_reg_value, int print_host)
+{
+ char host[16];
+
+ dprintk(KERN_WARNING "3w-xxxx: tw_decode_bits()\n");
+
+ if (print_host)
+ sprintf(host, " scsi%d:", tw_dev->host->host_no);
+ else
+ host[0] = '\0';
+
+ if (status_reg_value & TW_STATUS_PCI_PARITY_ERROR) {
+ printk(KERN_WARNING "3w-xxxx:%s PCI Parity Error: clearing.\n", host);
+ outl(TW_CONTROL_CLEAR_PARITY_ERROR, TW_CONTROL_REG_ADDR(tw_dev));
+ }
+
+ if (status_reg_value & TW_STATUS_PCI_ABORT) {
+ printk(KERN_WARNING "3w-xxxx:%s PCI Abort: clearing.\n", host);
+ outl(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) {
+ printk(KERN_WARNING "3w-xxxx:%s Controller Queue Error: clearing.\n", host);
+ outl(TW_CONTROL_CLEAR_QUEUE_ERROR, TW_CONTROL_REG_ADDR(tw_dev));
+ }
+
+ if (status_reg_value & TW_STATUS_SBUF_WRITE_ERROR) {
+ printk(KERN_WARNING "3w-xxxx:%s SBUF Write Error: clearing.\n", host);
+ outl(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) {
+ printk(KERN_WARNING "3w-xxxx:%s Microcontroller Error: clearing.\n", host);
+ tw_dev->reset_print = 1;
+ }
+ return 1;
+ }
+
+ return 0;
+} /* End tw_decode_bits() */
+
+/* This function will poll the status register for a flag */
+static int tw_poll_status(TW_Device_Extension *tw_dev, u32 flag, int seconds)
+{
+ u32 status_reg_value;
+ unsigned long before;
+ int retval = 1;
+
+ status_reg_value = inl(TW_STATUS_REG_ADDR(tw_dev));
+ before = jiffies;
+
+ if (tw_check_bits(status_reg_value))
+ tw_decode_bits(tw_dev, status_reg_value, 0);
+
+ while ((status_reg_value & flag) != flag) {
+ status_reg_value = inl(TW_STATUS_REG_ADDR(tw_dev));
+
+ if (tw_check_bits(status_reg_value))
+ tw_decode_bits(tw_dev, status_reg_value, 0);
+
+ if (time_after(jiffies, before + HZ * seconds))
+ goto out;
+
+ msleep(50);
+ }
+ retval = 0;
+out:
+ return retval;
+} /* End tw_poll_status() */
+
+/* This function will poll the status register for disappearance of a flag */
+static int tw_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 = inl(TW_STATUS_REG_ADDR(tw_dev));
+ before = jiffies;
+
+ if (tw_check_bits(status_reg_value))
+ tw_decode_bits(tw_dev, status_reg_value, 0);
+
+ while ((status_reg_value & flag) != 0) {
+ status_reg_value = inl(TW_STATUS_REG_ADDR(tw_dev));
+
+ if (tw_check_bits(status_reg_value))
+ tw_decode_bits(tw_dev, status_reg_value, 0);
+
+ if (time_after(jiffies, before + HZ * seconds))
+ goto out;
+
+ msleep(50);
+ }
+ retval = 0;
+out:
+ return retval;
+} /* End tw_poll_status_gone() */
+
+/* This function will attempt to post a command packet to the board */
+static int tw_post_command_packet(TW_Device_Extension *tw_dev, int request_id)
+{
+ u32 status_reg_value;
+ unsigned long command_que_value;
+
+ dprintk(KERN_NOTICE "3w-xxxx: tw_post_command_packet()\n");
+ command_que_value = tw_dev->command_packet_physical_address[request_id];
+ status_reg_value = inl(TW_STATUS_REG_ADDR(tw_dev));
+
+ if (tw_check_bits(status_reg_value)) {
+ dprintk(KERN_WARNING "3w-xxxx: tw_post_command_packet(): Unexpected bits.\n");
+ tw_decode_bits(tw_dev, status_reg_value, 1);
+ }
+
+ if ((status_reg_value & TW_STATUS_COMMAND_QUEUE_FULL) == 0) {
+ /* We successfully posted the command packet */
+ outl(command_que_value, TW_COMMAND_QUEUE_REG_ADDR(tw_dev));
+ 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;
+ }
+ } else {
+ /* Couldn't post the command packet, so we do it in the isr */
+ 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;
+ if (tw_dev->pending_tail == TW_Q_LENGTH-1) {
+ tw_dev->pending_tail = TW_Q_START;
+ } else {
+ tw_dev->pending_tail = tw_dev->pending_tail + 1;
+ }
+ }
+ TW_UNMASK_COMMAND_INTERRUPT(tw_dev);
+ return 1;
+ }
+ return 0;
+} /* End tw_post_command_packet() */
+
+/* This function will return valid sense buffer information for failed cmds */
+static int tw_decode_sense(TW_Device_Extension *tw_dev, int request_id, int fill_sense)
+{
+ int i;
+ TW_Command *command;
+
+ dprintk(KERN_WARNING "3w-xxxx: tw_decode_sense()\n");
+ command = (TW_Command *)tw_dev->command_packet_virtual_address[request_id];
+
+ printk(KERN_WARNING "3w-xxxx: scsi%d: Command failed: status = 0x%x, flags = 0x%x, unit #%d.\n", tw_dev->host->host_no, command->status, command->flags, TW_UNIT_OUT(command->unit__hostid));
+
+ /* Attempt to return intelligent sense information */
+ if (fill_sense) {
+ if ((command->status == 0xc7) || (command->status == 0xcb)) {
+ for (i=0;i<(sizeof(tw_sense_table)/sizeof(tw_sense_table[0]));i++) {
+ if (command->flags == tw_sense_table[i][0]) {
+
+ /* Valid bit and 'current errors' */
+ tw_dev->srb[request_id]->sense_buffer[0] = (0x1 << 7 | 0x70);
+
+ /* Sense key */
+ tw_dev->srb[request_id]->sense_buffer[2] = tw_sense_table[i][1];
+
+ /* Additional sense length */
+ tw_dev->srb[request_id]->sense_buffer[7] = 0xa; /* 10 bytes */
+
+ /* Additional sense code */
+ tw_dev->srb[request_id]->sense_buffer[12] = tw_sense_table[i][2];
+
+ /* Additional sense code qualifier */
+ tw_dev->srb[request_id]->sense_buffer[13] = tw_sense_table[i][3];
+
+ tw_dev->srb[request_id]->result = (DID_OK << 16) | (CHECK_CONDITION << 1);
+ return TW_ISR_DONT_RESULT; /* Special case for isr to not over-write result */
+ }
+ }
+ }
+
+ /* If no table match, error so we get a reset */
+ return 1;
+ }
+
+ return 0;
+} /* End tw_decode_sense() */
+
+/* This function will report controller error status */
+static int tw_check_errors(TW_Device_Extension *tw_dev)
+{
+ u32 status_reg_value;
+
+ status_reg_value = inl(TW_STATUS_REG_ADDR(tw_dev));
+
+ if (TW_STATUS_ERRORS(status_reg_value) || tw_check_bits(status_reg_value)) {
+ tw_decode_bits(tw_dev, status_reg_value, 0);
+ return 1;
+ }
+
+ return 0;
+} /* End tw_check_errors() */
+
+/* This function will empty the response que */
+static void tw_empty_response_que(TW_Device_Extension *tw_dev)
+{
+ u32 status_reg_value, response_que_value;
+
+ status_reg_value = inl(TW_STATUS_REG_ADDR(tw_dev));
+
+ while ((status_reg_value & TW_STATUS_RESPONSE_QUEUE_EMPTY) == 0) {
+ response_que_value = inl(TW_RESPONSE_QUEUE_REG_ADDR(tw_dev));
+ status_reg_value = inl(TW_STATUS_REG_ADDR(tw_dev));
+ }
+} /* End tw_empty_response_que() */
+
+/* This function will free a request_id */
+static void tw_state_request_finish(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 tw_state_request_finish() */
+
+/* This function will assign an available request_id */
+static void tw_state_request_start(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 tw_state_request_start() */
+
+/* Show some statistics about the card */
+static ssize_t tw_show_stats(struct class_device *class_dev, char *buf)
+{
+ struct Scsi_Host *host = class_to_shost(class_dev);
+ TW_Device_Extension *tw_dev = (TW_Device_Extension *)host->hostdata;
+ unsigned long flags = 0;
+ ssize_t len;
+
+ spin_lock_irqsave(tw_dev->host->host_lock, flags);
+ len = snprintf(buf, PAGE_SIZE, "3w-xxxx Driver version: %s\n"
+ "Current commands posted: %4d\n"
+ "Max commands posted: %4d\n"
+ "Current pending commands: %4d\n"
+ "Max pending commands: %4d\n"
+ "Last sgl length: %4d\n"
+ "Max sgl length: %4d\n"
+ "Last sector count: %4d\n"
+ "Max sector count: %4d\n"
+ "SCSI Host Resets: %4d\n"
+ "AEN's: %4d\n",
+ TW_DRIVER_VERSION,
+ tw_dev->posted_request_count,
+ tw_dev->max_posted_request_count,
+ tw_dev->pending_request_count,
+ tw_dev->max_pending_request_count,
+ tw_dev->sgl_entries,
+ tw_dev->max_sgl_entries,
+ tw_dev->sector_count,
+ tw_dev->max_sector_count,
+ tw_dev->num_resets,
+ tw_dev->aen_count);
+ spin_unlock_irqrestore(tw_dev->host->host_lock, flags);
+ return len;
+} /* End tw_show_stats() */
+
+/* This function will set a devices queue depth */
+static int tw_change_queue_depth(struct scsi_device *sdev, int queue_depth)
+{
+ if (queue_depth > TW_Q_LENGTH-2)
+ queue_depth = TW_Q_LENGTH-2;
+ scsi_adjust_queue_depth(sdev, MSG_ORDERED_TAG, queue_depth);
+ return queue_depth;
+} /* End tw_change_queue_depth() */
+
+/* Create sysfs 'stats' entry */
+static struct class_device_attribute tw_host_stats_attr = {
+ .attr = {
+ .name = "stats",
+ .mode = S_IRUGO,
+ },
+ .show = tw_show_stats
+};
+
+/* Host attributes initializer */
+static struct class_device_attribute *tw_host_attrs[] = {
+ &tw_host_stats_attr,
+ NULL,
+};
+
+/* This function will read the aen queue from the isr */
+static int tw_aen_read_queue(TW_Device_Extension *tw_dev, int request_id)
+{
+ TW_Command *command_packet;
+ TW_Param *param;
+ unsigned long command_que_value;
+ u32 status_reg_value;
+ unsigned long param_value = 0;
+
+ dprintk(KERN_NOTICE "3w-xxxx: tw_aen_read_queue()\n");
+
+ status_reg_value = inl(TW_STATUS_REG_ADDR(tw_dev));
+ if (tw_check_bits(status_reg_value)) {
+ dprintk(KERN_WARNING "3w-xxxx: tw_aen_read_queue(): Unexpected bits.\n");
+ tw_decode_bits(tw_dev, status_reg_value, 1);
+ return 1;
+ }
+ if (tw_dev->command_packet_virtual_address[request_id] == NULL) {
+ printk(KERN_WARNING "3w-xxxx: tw_aen_read_queue(): Bad command packet virtual address.\n");
+ return 1;
+ }
+ command_packet = (TW_Command *)tw_dev->command_packet_virtual_address[request_id];
+ memset(command_packet, 0, sizeof(TW_Sector));
+ command_packet->opcode__sgloffset = TW_OPSGL_IN(2, TW_OP_GET_PARAM);
+ command_packet->size = 4;
+ command_packet->request_id = request_id;
+ command_packet->status = 0;
+ command_packet->flags = 0;
+ command_packet->byte6.parameter_count = 1;
+ command_que_value = tw_dev->command_packet_physical_address[request_id];
+ if (command_que_value == 0) {
+ printk(KERN_WARNING "3w-xxxx: tw_aen_read_queue(): Bad command packet physical address.\n");
+ return 1;
+ }
+ /* Now setup the param */
+ if (tw_dev->alignment_virtual_address[request_id] == NULL) {
+ printk(KERN_WARNING "3w-xxxx: tw_aen_read_queue(): Bad alignment virtual address.\n");
+ return 1;
+ }
+ param = (TW_Param *)tw_dev->alignment_virtual_address[request_id];
+ memset(param, 0, sizeof(TW_Sector));
+ param->table_id = 0x401; /* AEN table */
+ param->parameter_id = 2; /* Unit code */
+ param->parameter_size_bytes = 2;
+ param_value = tw_dev->alignment_physical_address[request_id];
+ if (param_value == 0) {
+ printk(KERN_WARNING "3w-xxxx: tw_aen_read_queue(): Bad alignment physical address.\n");
+ return 1;
+ }
+ command_packet->byte8.param.sgl[0].address = param_value;
+ command_packet->byte8.param.sgl[0].length = sizeof(TW_Sector);
+
+ /* Now post the command packet */
+ if ((status_reg_value & TW_STATUS_COMMAND_QUEUE_FULL) == 0) {
+ dprintk(KERN_WARNING "3w-xxxx: tw_aen_read_queue(): Post succeeded.\n");
+ tw_dev->srb[request_id] = NULL; /* Flag internal command */
+ tw_dev->state[request_id] = TW_S_POSTED;
+ outl(command_que_value, TW_COMMAND_QUEUE_REG_ADDR(tw_dev));
+ } else {
+ printk(KERN_WARNING "3w-xxxx: tw_aen_read_queue(): Post failed, will retry.\n");
+ return 1;
+ }
+
+ return 0;
+} /* End tw_aen_read_queue() */
+
+/* This function will complete an aen request from the isr */
+static int tw_aen_complete(TW_Device_Extension *tw_dev, int request_id)
+{
+ TW_Param *param;
+ unsigned short aen;
+ int error = 0, table_max = 0;
+
+ dprintk(KERN_WARNING "3w-xxxx: tw_aen_complete()\n");
+ if (tw_dev->alignment_virtual_address[request_id] == NULL) {
+ printk(KERN_WARNING "3w-xxxx: tw_aen_complete(): Bad alignment virtual address.\n");
+ return 1;
+ }
+ param = (TW_Param *)tw_dev->alignment_virtual_address[request_id];
+ aen = *(unsigned short *)(param->data);
+ dprintk(KERN_NOTICE "3w-xxxx: tw_aen_complete(): Queue'd code 0x%x\n", aen);
+
+ /* Print some useful info when certain aen codes come out */
+ if (aen == 0x0ff) {
+ printk(KERN_WARNING "3w-xxxx: scsi%d: AEN: INFO: AEN queue overflow.\n", tw_dev->host->host_no);
+ } else {
+ table_max = sizeof(tw_aen_string)/sizeof(char *);
+ if ((aen & 0x0ff) < table_max) {
+ if ((tw_aen_string[aen & 0xff][strlen(tw_aen_string[aen & 0xff])-1]) == '#') {
+ printk(KERN_WARNING "3w-xxxx: scsi%d: AEN: %s%d.\n", tw_dev->host->host_no, tw_aen_string[aen & 0xff], aen >> 8);
+ } else {
+ if (aen != 0x0)
+ printk(KERN_WARNING "3w-xxxx: scsi%d: AEN: %s.\n", tw_dev->host->host_no, tw_aen_string[aen & 0xff]);
+ }
+ } else {
+ printk(KERN_WARNING "3w-xxxx: scsi%d: Received AEN %d.\n", tw_dev->host->host_no, aen);
+ }
+ }
+ if (aen != TW_AEN_QUEUE_EMPTY) {
+ tw_dev->aen_count++;
+
+ /* Now queue the code */
+ tw_dev->aen_queue[tw_dev->aen_tail] = aen;
+ if (tw_dev->aen_tail == TW_Q_LENGTH - 1) {
+ tw_dev->aen_tail = TW_Q_START;
+ } else {
+ tw_dev->aen_tail = tw_dev->aen_tail + 1;
+ }
+ if (tw_dev->aen_head == tw_dev->aen_tail) {
+ if (tw_dev->aen_head == TW_Q_LENGTH - 1) {
+ tw_dev->aen_head = TW_Q_START;
+ } else {
+ tw_dev->aen_head = tw_dev->aen_head + 1;
+ }
+ }
+
+ error = tw_aen_read_queue(tw_dev, request_id);
+ if (error) {
+ printk(KERN_WARNING "3w-xxxx: scsi%d: Error completing AEN.\n", tw_dev->host->host_no);
+ tw_dev->state[request_id] = TW_S_COMPLETED;
+ tw_state_request_finish(tw_dev, request_id);
+ }
+ } else {
+ tw_dev->state[request_id] = TW_S_COMPLETED;
+ tw_state_request_finish(tw_dev, request_id);
+ }
+
+ return 0;
+} /* End tw_aen_complete() */
+
+/* This function will drain the aen queue after a soft reset */
+static int tw_aen_drain_queue(TW_Device_Extension *tw_dev)
+{
+ TW_Command *command_packet;
+ TW_Param *param;
+ int request_id = 0;
+ unsigned long command_que_value;
+ unsigned long param_value;
+ TW_Response_Queue response_queue;
+ unsigned short aen;
+ unsigned short aen_code;
+ int finished = 0;
+ int first_reset = 0;
+ int queue = 0;
+ int found = 0, table_max = 0;
+
+ dprintk(KERN_NOTICE "3w-xxxx: tw_aen_drain_queue()\n");
+
+ if (tw_poll_status(tw_dev, TW_STATUS_ATTENTION_INTERRUPT | TW_STATUS_MICROCONTROLLER_READY, 30)) {
+ dprintk(KERN_WARNING "3w-xxxx: tw_aen_drain_queue(): No attention interrupt for card %d.\n", tw_device_extension_count);
+ return 1;
+ }
+ TW_CLEAR_ATTENTION_INTERRUPT(tw_dev);
+
+ /* Empty response queue */
+ tw_empty_response_que(tw_dev);
+
+ /* Initialize command packet */
+ if (tw_dev->command_packet_virtual_address[request_id] == NULL) {
+ printk(KERN_WARNING "3w-xxxx: tw_aen_drain_queue(): Bad command packet virtual address.\n");
+ return 1;
+ }
+ command_packet = (TW_Command *)tw_dev->command_packet_virtual_address[request_id];
+ memset(command_packet, 0, sizeof(TW_Sector));
+ command_packet->opcode__sgloffset = TW_OPSGL_IN(2, TW_OP_GET_PARAM);
+ command_packet->size = 4;
+ command_packet->request_id = request_id;
+ command_packet->status = 0;
+ command_packet->flags = 0;
+ command_packet->byte6.parameter_count = 1;
+ command_que_value = tw_dev->command_packet_physical_address[request_id];
+ if (command_que_value == 0) {
+ printk(KERN_WARNING "3w-xxxx: tw_aen_drain_queue(): Bad command packet physical address.\n");
+ return 1;
+ }
+
+ /* Now setup the param */
+ if (tw_dev->alignment_virtual_address[request_id] == NULL) {
+ printk(KERN_WARNING "3w-xxxx: tw_aen_drain_queue(): Bad alignment virtual address.\n");
+ return 1;
+ }
+ param = (TW_Param *)tw_dev->alignment_virtual_address[request_id];
+ memset(param, 0, sizeof(TW_Sector));
+ param->table_id = 0x401; /* AEN table */
+ param->parameter_id = 2; /* Unit code */
+ param->parameter_size_bytes = 2;
+ param_value = tw_dev->alignment_physical_address[request_id];
+ if (param_value == 0) {
+ printk(KERN_WARNING "3w-xxxx: tw_aen_drain_queue(): Bad alignment physical address.\n");
+ return 1;
+ }
+ command_packet->byte8.param.sgl[0].address = param_value;
+ command_packet->byte8.param.sgl[0].length = sizeof(TW_Sector);
+
+ /* Now drain the controller's aen queue */
+ do {
+ /* Post command packet */
+ outl(command_que_value, TW_COMMAND_QUEUE_REG_ADDR(tw_dev));
+
+ /* Now poll for completion */
+ if (tw_poll_status_gone(tw_dev, TW_STATUS_RESPONSE_QUEUE_EMPTY, 30) == 0) {
+ response_queue.value = inl(TW_RESPONSE_QUEUE_REG_ADDR(tw_dev));
+ request_id = TW_RESID_OUT(response_queue.response_id);
+
+ if (request_id != 0) {
+ /* Unexpected request id */
+ printk(KERN_WARNING "3w-xxxx: tw_aen_drain_queue(): Unexpected request id.\n");
+ return 1;
+ }
+
+ if (command_packet->status != 0) {
+ if (command_packet->flags != TW_AEN_TABLE_UNDEFINED) {
+ /* Bad response */
+ tw_decode_sense(tw_dev, request_id, 0);
+ return 1;
+ } else {
+ /* We know this is a 3w-1x00, and doesn't support aen's */
+ return 0;
+ }
+ }
+
+ /* Now check the aen */
+ aen = *(unsigned short *)(param->data);
+ aen_code = (aen & 0x0ff);
+ queue = 0;
+ switch (aen_code) {
+ case TW_AEN_QUEUE_EMPTY:
+ dprintk(KERN_WARNING "3w-xxxx: AEN: %s.\n", tw_aen_string[aen & 0xff]);
+ if (first_reset != 1) {
+ return 1;
+ } else {
+ finished = 1;
+ }
+ break;
+ case TW_AEN_SOFT_RESET:
+ if (first_reset == 0) {
+ first_reset = 1;
+ } else {
+ printk(KERN_WARNING "3w-xxxx: AEN: %s.\n", tw_aen_string[aen & 0xff]);
+ tw_dev->aen_count++;
+ queue = 1;
+ }
+ break;
+ default:
+ if (aen == 0x0ff) {
+ printk(KERN_WARNING "3w-xxxx: AEN: INFO: AEN queue overflow.\n");
+ } else {
+ table_max = sizeof(tw_aen_string)/sizeof(char *);
+ if ((aen & 0x0ff) < table_max) {
+ if ((tw_aen_string[aen & 0xff][strlen(tw_aen_string[aen & 0xff])-1]) == '#') {
+ printk(KERN_WARNING "3w-xxxx: AEN: %s%d.\n", tw_aen_string[aen & 0xff], aen >> 8);
+ } else {
+ printk(KERN_WARNING "3w-xxxx: AEN: %s.\n", tw_aen_string[aen & 0xff]);
+ }
+ } else
+ printk(KERN_WARNING "3w-xxxx: Received AEN %d.\n", aen);
+ }
+ tw_dev->aen_count++;
+ queue = 1;
+ }
+
+ /* Now put the aen on the aen_queue */
+ if (queue == 1) {
+ tw_dev->aen_queue[tw_dev->aen_tail] = aen;
+ if (tw_dev->aen_tail == TW_Q_LENGTH - 1) {
+ tw_dev->aen_tail = TW_Q_START;
+ } else {
+ tw_dev->aen_tail = tw_dev->aen_tail + 1;
+ }
+ if (tw_dev->aen_head == tw_dev->aen_tail) {
+ if (tw_dev->aen_head == TW_Q_LENGTH - 1) {
+ tw_dev->aen_head = TW_Q_START;
+ } else {
+ tw_dev->aen_head = tw_dev->aen_head + 1;
+ }
+ }
+ }
+ found = 1;
+ }
+ if (found == 0) {
+ printk(KERN_WARNING "3w-xxxx: tw_aen_drain_queue(): Response never received.\n");
+ return 1;
+ }
+ } while (finished == 0);
+
+ return 0;
+} /* End tw_aen_drain_queue() */
+
+/* This function will allocate memory */
+static int tw_allocate_memory(TW_Device_Extension *tw_dev, int size, int which)
+{
+ int i;
+ dma_addr_t dma_handle;
+ unsigned long *cpu_addr = NULL;
+
+ dprintk(KERN_NOTICE "3w-xxxx: tw_allocate_memory()\n");
+
+ cpu_addr = pci_alloc_consistent(tw_dev->tw_pci_dev, size*TW_Q_LENGTH, &dma_handle);
+ if (cpu_addr == NULL) {
+ printk(KERN_WARNING "3w-xxxx: pci_alloc_consistent() failed.\n");
+ return 1;
+ }
+
+ if ((unsigned long)cpu_addr % (tw_dev->tw_pci_dev->device == TW_DEVICE_ID ? TW_ALIGNMENT_6000 : TW_ALIGNMENT_7000)) {
+ printk(KERN_WARNING "3w-xxxx: Couldn't allocate correctly aligned memory.\n");
+ pci_free_consistent(tw_dev->tw_pci_dev, size*TW_Q_LENGTH, cpu_addr, dma_handle);
+ return 1;
+ }
+
+ memset(cpu_addr, 0, size*TW_Q_LENGTH);
+
+ for (i=0;i<TW_Q_LENGTH;i++) {
+ switch(which) {
+ case 0:
+ tw_dev->command_packet_physical_address[i] = dma_handle+(i*size);
+ tw_dev->command_packet_virtual_address[i] = (unsigned long *)((unsigned char *)cpu_addr + (i*size));
+ break;
+ case 1:
+ tw_dev->alignment_physical_address[i] = dma_handle+(i*size);
+ tw_dev->alignment_virtual_address[i] = (unsigned long *)((unsigned char *)cpu_addr + (i*size));
+ break;
+ default:
+ printk(KERN_WARNING "3w-xxxx: tw_allocate_memory(): case slip in tw_allocate_memory()\n");
+ return 1;
+ }
+ }
+
+ return 0;
+} /* End tw_allocate_memory() */
+
+/* This function handles ioctl for the character device */
+static int tw_chrdev_ioctl(struct inode *inode, struct file *file, unsigned int cmd, unsigned long arg)
+{
+ int request_id;
+ dma_addr_t dma_handle;
+ unsigned short tw_aen_code;
+ unsigned long flags;
+ unsigned int data_buffer_length = 0;
+ unsigned long data_buffer_length_adjusted = 0;
+ unsigned long *cpu_addr;
+ long timeout;
+ TW_New_Ioctl *tw_ioctl;
+ TW_Passthru *passthru;
+ TW_Device_Extension *tw_dev = tw_device_extension_list[iminor(inode)];
+ int retval = -EFAULT;
+ void __user *argp = (void __user *)arg;
+
+ dprintk(KERN_WARNING "3w-xxxx: tw_chrdev_ioctl()\n");
+
+ /* Only let one of these through at a time */
+ if (down_interruptible(&tw_dev->ioctl_sem))
+ return -EINTR;
+
+ /* First copy down the buffer length */
+ if (copy_from_user(&data_buffer_length, argp, sizeof(unsigned int)))
+ goto out;
+
+ /* Check size */
+ if (data_buffer_length > TW_MAX_IOCTL_SECTORS * 512) {
+ retval = -EINVAL;
+ goto out;
+ }
+
+ /* Hardware can only do multiple of 512 byte transfers */
+ data_buffer_length_adjusted = (data_buffer_length + 511) & ~511;
+
+ /* Now allocate ioctl buf memory */
+ cpu_addr = dma_alloc_coherent(&tw_dev->tw_pci_dev->dev, data_buffer_length_adjusted+sizeof(TW_New_Ioctl) - 1, &dma_handle, GFP_KERNEL);
+ if (cpu_addr == NULL) {
+ retval = -ENOMEM;
+ goto out;
+ }
+
+ tw_ioctl = (TW_New_Ioctl *)cpu_addr;
+
+ /* Now copy down the entire ioctl */
+ if (copy_from_user(tw_ioctl, argp, data_buffer_length + sizeof(TW_New_Ioctl) - 1))
+ goto out2;
+
+ passthru = (TW_Passthru *)&tw_ioctl->firmware_command;
+
+ /* See which ioctl we are doing */
+ switch (cmd) {
+ case TW_OP_NOP:
+ dprintk(KERN_WARNING "3w-xxxx: tw_chrdev_ioctl(): caught TW_OP_NOP.\n");
+ break;
+ case TW_OP_AEN_LISTEN:
+ dprintk(KERN_WARNING "3w-xxxx: tw_chrdev_ioctl(): caught TW_AEN_LISTEN.\n");
+ memset(tw_ioctl->data_buffer, 0, data_buffer_length);
+
+ spin_lock_irqsave(tw_dev->host->host_lock, flags);
+ if (tw_dev->aen_head == tw_dev->aen_tail) {
+ tw_aen_code = TW_AEN_QUEUE_EMPTY;
+ } else {
+ tw_aen_code = tw_dev->aen_queue[tw_dev->aen_head];
+ if (tw_dev->aen_head == TW_Q_LENGTH - 1) {
+ tw_dev->aen_head = TW_Q_START;
+ } else {
+ tw_dev->aen_head = tw_dev->aen_head + 1;
+ }
+ }
+ spin_unlock_irqrestore(tw_dev->