diff options
author | Linus Torvalds <torvalds@ppc970.osdl.org> | 2005-04-16 15:20:36 -0700 |
---|---|---|
committer | Linus Torvalds <torvalds@ppc970.osdl.org> | 2005-04-16 15:20:36 -0700 |
commit | 1da177e4c3f41524e886b7f1b8a0c1fc7321cac2 (patch) | |
tree | 0bba044c4ce775e45a88a51686b5d9f90697ea9d /drivers/scsi/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.c | 2478 |
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-> |