blob: ead8e40824197ccbbced6c9de6f969d4533d528f [file] [log] [blame]
/*
3w-xxxx.c -- 3ware Storage Controller device driver for Linux.
Written By: Adam Radford <linux@3ware.com>
Modifications By: Joel Jacobson <linux@3ware.com>
Arnaldo Carvalho de Melo <acme@conectiva.com.br>
Brad Strand <linux@3ware.com>
Copyright (C) 1999-2003 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:
linux@3ware.com
For more information, goto:
http://www.3ware.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.
*/
#include <linux/module.h>
MODULE_AUTHOR ("3ware Inc.");
#ifdef __SMP__
MODULE_DESCRIPTION ("3ware Storage Controller Linux Driver (SMP)");
#else
MODULE_DESCRIPTION ("3ware 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/blk.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 <asm/errno.h>
#include <asm/io.h>
#include <asm/irq.h>
#include <asm/uaccess.h>
#define __3W_C /* let 3w-xxxx.h know it is use */
#include "scsi.h"
#include "hosts.h"
#include "3w-xxxx.h"
static int tw_chrdev_ioctl(struct inode *inode, struct file *file, unsigned int cmd, unsigned long arg);
static int tw_chrdev_open(struct inode *inode, struct file *file);
static int tw_chrdev_release(struct inode *inode, struct file *file);
static int tw_copy_info(TW_Info *info, char *fmt, ...);
static void tw_copy_mem_info(TW_Info *info, char *data, int len);
static void tw_interrupt(int irq, void *dev_instance, struct pt_regs *regs);
static int tw_halt(struct notifier_block *nb, ulong event, void *buf);
static int tw_map_scsi_sg_data(struct pci_dev *pdev, Scsi_Cmnd *cmd);
static u32 tw_map_scsi_single_data(struct pci_dev *pdev, Scsi_Cmnd *cmd);
static void tw_unmap_scsi_data(struct pci_dev *pdev, Scsi_Cmnd *cmd);
/* Notifier block to get a notify on system shutdown/halt/reboot */
static struct notifier_block tw_notifier = {
tw_halt, NULL, 0
};
/* File operations struct for character device */
static struct file_operations tw_fops = {
owner: THIS_MODULE,
ioctl: tw_chrdev_ioctl,
open: tw_chrdev_open,
release: tw_chrdev_release
};
/* Globals */
char *tw_driver_version="1.02.00.032";
TW_Device_Extension *tw_device_extension_list[TW_MAX_SLOT];
int tw_device_extension_count = 0;
static int twe_major = -1;
/* Functions */
/* This function will complete an aen request from the isr */
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 != 0x0)
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;
}
}
if (aen != TW_AEN_QUEUE_EMPTY) {
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 */
int tw_aen_drain_queue(TW_Device_Extension *tw_dev)
{
TW_Command *command_packet;
TW_Param *param;
int request_id = 0;
u32 command_que_addr;
unsigned long command_que_value;
unsigned long param_value;
TW_Response_Queue response_queue;
u32 response_que_addr;
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");
command_que_addr = tw_dev->registers.command_que_addr;
response_que_addr = tw_dev->registers.response_que_addr;
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->byte0.opcode = TW_OP_GET_PARAM;
command_packet->byte0.sgl_offset = 2;
command_packet->size = 4;
command_packet->request_id = request_id;
command_packet->byte3.unit = 0;
command_packet->byte3.host_id = 0;
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, command_que_addr);
/* Now poll for completion */
if (tw_poll_status_gone(tw_dev, TW_STATUS_RESPONSE_QUEUE_EMPTY, 30) == 0) {
response_queue.value = inl(response_que_addr);
request_id = (unsigned char)response_queue.u.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 read the aen queue from the isr */
int tw_aen_read_queue(TW_Device_Extension *tw_dev, int request_id)
{
TW_Command *command_packet;
TW_Param *param;
u32 command_que_addr;
unsigned long command_que_value;
u32 status_reg_value = 0, status_reg_addr;
unsigned long param_value = 0;
dprintk(KERN_NOTICE "3w-xxxx: tw_aen_read_queue()\n");
command_que_addr = tw_dev->registers.command_que_addr;
status_reg_addr = tw_dev->registers.status_reg_addr;
status_reg_value = inl(status_reg_addr);
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->byte0.opcode = TW_OP_GET_PARAM;
command_packet->byte0.sgl_offset = 2;
command_packet->size = 4;
command_packet->request_id = request_id;
command_packet->byte3.unit = 0;
command_packet->byte3.host_id = 0;
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] = 0; /* Flag internal command */
tw_dev->state[request_id] = TW_S_POSTED;
outl(command_que_value, command_que_addr);
} 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 allocate memory */
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");
for (i=0;i<TW_Q_LENGTH;i++) {
cpu_addr = pci_alloc_consistent(tw_dev->tw_pci_dev, size, &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, cpu_addr, dma_handle);
return 1;
}
switch(which) {
case 0:
tw_dev->command_packet_virtual_address[i] = cpu_addr;
tw_dev->command_packet_physical_address[i] = dma_handle;
memset(tw_dev->command_packet_virtual_address[i], 0, size);
break;
case 1:
tw_dev->alignment_virtual_address[i] = cpu_addr;
tw_dev->alignment_physical_address[i] = dma_handle;
memset(tw_dev->alignment_virtual_address[i], 0, 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 will check the status register for unexpected bits */
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 report controller error status */
int tw_check_errors(TW_Device_Extension *tw_dev)
{
u32 status_reg_addr, status_reg_value;
status_reg_addr = tw_dev->registers.status_reg_addr;
status_reg_value = inl(status_reg_addr);
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 handles ioctl for the character device */
static int tw_chrdev_ioctl(struct inode *inode, struct file *file, unsigned int cmd, unsigned long arg)
{
int error, request_id;
dma_addr_t dma_handle;
unsigned short tw_aen_code;
unsigned long before;
unsigned long flags;
unsigned int data_buffer_length = 0;
unsigned long data_buffer_length_adjusted = 0;
unsigned long *cpu_addr;
TW_New_Ioctl *tw_ioctl;
TW_Passthru *passthru;
TW_Device_Extension *tw_dev = tw_device_extension_list[minor(inode->i_rdev)];
int retval = -EFAULT;
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 */
error = copy_from_user(&data_buffer_length, (void *)arg, sizeof(unsigned int));
if (error)
goto out;
/* Check size */
if (data_buffer_length > TW_MAX_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 = pci_alloc_consistent(tw_dev->tw_pci_dev, data_buffer_length_adjusted+sizeof(TW_New_Ioctl) - 1, &dma_handle);
if (cpu_addr == NULL) {
retval = -ENOMEM;
goto out;
}
tw_ioctl = (TW_New_Ioctl *)cpu_addr;
/* Now copy down the entire ioctl */
error = copy_from_user(tw_ioctl, (void *)arg, data_buffer_length + sizeof(TW_New_Ioctl) - 1);
if (error)
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, tw_ioctl->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->host->host_lock, flags);
memcpy(tw_ioctl->data_buffer, &tw_aen_code, sizeof(tw_aen_code));
break;
case TW_CMD_PACKET_WITH_DATA:
dprintk(KERN_WARNING "3w-xxxx: tw_chrdev_ioctl(): caught TW_CMD_PACKET_WITH_DATA.\n");
spin_lock_irqsave(&tw_dev->tw_lock, flags);
tw_state_request_start(tw_dev, &request_id);
/* Flag internal command */
tw_dev->srb[request_id] = 0;
/* Flag chrdev ioctl */
tw_dev->chrdev_request_id = request_id;
tw_ioctl->firmware_command.request_id = request_id;
/* Load the sg list */
switch (tw_ioctl->firmware_command.byte0.sgl_offset) {
case 2:
tw_ioctl->firmware_command.byte8.param.sgl[0].address = dma_handle + sizeof(TW_New_Ioctl) - 1;
tw_ioctl->firmware_command.byte8.param.sgl[0].length = data_buffer_length_adjusted;
break;
case 3:
tw_ioctl->firmware_command.byte8.io.sgl[0].address = dma_handle + sizeof(TW_New_Ioctl) - 1;
tw_ioctl->firmware_command.byte8.io.sgl[0].length = data_buffer_length_adjusted;
break;
case 5:
passthru->sg_list[0].address = dma_handle + sizeof(TW_New_Ioctl) - 1;
passthru->sg_list[0].length = data_buffer_length_adjusted;
break;
}
memcpy(tw_dev->command_packet_virtual_address[request_id], &(tw_ioctl->firmware_command), sizeof(TW_Command));
/* Now post the command packet to the controller */
tw_post_command_packet(tw_dev, request_id);
spin_unlock_irqrestore(&tw_dev->tw_lock, flags);
/* Now wait for the command to complete */
before = jiffies;
while (tw_dev->chrdev_request_id != TW_IOCTL_CHRDEV_FREE)
{
/* FIXME: race condition on the wait*/
interruptible_sleep_on_timeout(&tw_dev->ioctl_wqueue, 1);
if (time_after(jiffies, before + HZ *TW_IOCTL_CHRDEV_TIMEOUT)) {
/* Now we need to reset the board */
printk(KERN_WARNING "3w-xxxx: scsi%d: Character ioctl (0x%x) timed out, resetting card.\n", tw_dev->host->host_no, cmd);
spin_lock_irqsave(&tw_dev->tw_lock, flags);
tw_dev->state[request_id] = TW_S_COMPLETED;
tw_state_request_finish(tw_dev, request_id);
tw_dev->posted_request_count--;
if (tw_reset_device_extension(tw_dev)) {
printk(KERN_WARNING "3w-xxxx: tw_chrdev_ioctl(): Reset failed for card %d.\n", tw_dev->host->host_no);
}
spin_unlock_irqrestore(&tw_dev->tw_lock, flags);
if (signal_pending(current))
retval = -EINTR;
else
retval = -EIO;
goto out2;
}
}
/* Now copy in the command packet response */
memcpy(&(tw_ioctl->firmware_command), tw_dev->command_packet_virtual_address[request_id], sizeof(TW_Command));
/* Now complete the io */
spin_lock_irqsave(&tw_dev->tw_lock, flags);
tw_dev->posted_request_count--;
tw_dev->state[request_id] = TW_S_COMPLETED;
tw_state_request_finish(tw_dev, request_id);
spin_unlock_irqrestore(&tw_dev->tw_lock, flags);
break;
default:
retval = -ENOTTY;
goto out2;
}
/* Now copy the response to userspace */
error = copy_to_user((void *)arg, tw_ioctl, sizeof(TW_New_Ioctl) + tw_ioctl->data_buffer_length - 1);
if (error == 0)
retval = 0;
out2:
/* Now free ioctl buf memory */
pci_free_consistent(tw_dev->tw_pci_dev, data_buffer_length_adjusted+sizeof(TW_New_Ioctl) - 1, cpu_addr, dma_handle);
out:
up(&tw_dev->ioctl_sem);
return retval;
} /* End tw_chrdev_ioctl() */
/* This function handles open for the character device */
static int tw_chrdev_open(struct inode *inode, struct file *file)
{
unsigned int minor_number;
dprintk(KERN_WARNING "3w-xxxx: tw_ioctl_open()\n");
minor_number = minor(inode->i_rdev);
if (minor_number >= tw_device_extension_count)
return -ENODEV;
return 0;
} /* End tw_chrdev_open() */
/* This function handles close for the character device */
static int tw_chrdev_release(struct inode *inode, struct file *file)
{
dprintk(KERN_WARNING "3w-xxxx: tw_ioctl_release()\n");
return 0;
} /* End tw_chrdev_release() */
/* This function will clear all interrupts on the controller */
void tw_clear_all_interrupts(TW_Device_Extension *tw_dev)
{
u32 control_reg_addr, control_reg_value;
control_reg_addr = tw_dev->registers.control_reg_addr;
control_reg_value = TW_STATUS_VALID_INTERRUPT;
outl(control_reg_value, control_reg_addr);
} /* End tw_clear_all_interrupts() */
/* This function will clear the attention interrupt */
void tw_clear_attention_interrupt(TW_Device_Extension *tw_dev)
{
u32 control_reg_addr, control_reg_value;
control_reg_addr = tw_dev->registers.control_reg_addr;
control_reg_value = TW_CONTROL_CLEAR_ATTENTION_INTERRUPT;
outl(control_reg_value, control_reg_addr);
} /* End tw_clear_attention_interrupt() */
/* This function will clear the host interrupt */
void tw_clear_host_interrupt(TW_Device_Extension *tw_dev)
{
u32 control_reg_addr, control_reg_value;
control_reg_addr = tw_dev->registers.control_reg_addr;
control_reg_value = TW_CONTROL_CLEAR_HOST_INTERRUPT;
outl(control_reg_value, control_reg_addr);
} /* End tw_clear_host_interrupt() */
/* This function is called by tw_scsi_proc_info */
static int tw_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);
tw_copy_mem_info(info, buf, len);
return len;
} /* End tw_copy_info() */
/* This function is called by tw_scsi_proc_info */
static void tw_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 tw_copy_mem_info() */
/* This function will print readable messages from status register errors */
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';
switch (status_reg_value & TW_STATUS_UNEXPECTED_BITS) {
case TW_STATUS_PCI_PARITY_ERROR:
printk(KERN_WARNING "3w-xxxx:%s PCI Parity Error: clearing.\n", host);
outl(TW_CONTROL_CLEAR_PARITY_ERROR, tw_dev->registers.control_reg_addr);
break;
case 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;
case TW_STATUS_PCI_ABORT:
printk(KERN_WARNING "3w-xxxx:%s PCI Abort: clearing.\n", host);
outl(TW_CONTROL_CLEAR_PCI_ABORT, tw_dev->registers.control_reg_addr);
pci_write_config_word(tw_dev->tw_pci_dev, PCI_STATUS, TW_PCI_CLEAR_PCI_ABORT);
break;
case TW_STATUS_QUEUE_ERROR:
printk(KERN_WARNING "3w-xxxx:%s Controller Queue Error: clearing.\n", host);
outl(TW_CONTROL_CLEAR_QUEUE_ERROR, tw_dev->registers.control_reg_addr);
break;
case 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_dev->registers.control_reg_addr);
break;
}
return 0;
} /* End tw_decode_bits() */
/* This function will return valid sense buffer information for failed cmds */
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, command->byte3.unit);
/* 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 disable interrupts on the controller */
void tw_disable_interrupts(TW_Device_Extension *tw_dev)
{
u32 control_reg_value, control_reg_addr;
control_reg_addr = tw_dev->registers.control_reg_addr;
control_reg_value = TW_CONTROL_DISABLE_INTERRUPTS;
outl(control_reg_value, control_reg_addr);
} /* End tw_disable_interrupts() */
/* This function will empty the response que */
void tw_empty_response_que(TW_Device_Extension *tw_dev)
{
u32 status_reg_addr, status_reg_value;
u32 response_que_addr, response_que_value;
status_reg_addr = tw_dev->registers.status_reg_addr;
response_que_addr = tw_dev->registers.response_que_addr;
status_reg_value = inl(status_reg_addr);
while ((status_reg_value & TW_STATUS_RESPONSE_QUEUE_EMPTY) == 0) {
response_que_value = inl(response_que_addr);
status_reg_value = inl(status_reg_addr);
}
} /* End tw_empty_response_que() */
/* This function will enable interrupts on the controller */
void tw_enable_interrupts(TW_Device_Extension *tw_dev)
{
u32 control_reg_value, control_reg_addr;
control_reg_addr = tw_dev->registers.control_reg_addr;
control_reg_value = (TW_CONTROL_ENABLE_INTERRUPTS |
TW_CONTROL_UNMASK_RESPONSE_INTERRUPT);
outl(control_reg_value, control_reg_addr);
} /* End tw_enable_interrupts() */
/* This function will enable interrupts on the controller */
void tw_enable_and_clear_interrupts(TW_Device_Extension *tw_dev)
{
u32 control_reg_value, control_reg_addr;
control_reg_addr = tw_dev->registers.control_reg_addr;
control_reg_value = (TW_CONTROL_CLEAR_ATTENTION_INTERRUPT |
TW_CONTROL_UNMASK_RESPONSE_INTERRUPT |
TW_CONTROL_ENABLE_INTERRUPTS);
outl(control_reg_value, control_reg_addr);
} /* End tw_enable_and_clear_interrupts() */
/* This function will find and initialize all cards */
int tw_findcards(Scsi_Host_Template *tw_host)
{
int numcards = 0, tries = 0, error = 0;
struct Scsi_Host *host;
TW_Device_Extension *tw_dev;
TW_Device_Extension *tw_dev2;
struct pci_dev *tw_pci_dev = NULL;
u32 status_reg_value;
unsigned char c = 1;
int i, j = -1;
u16 device[TW_NUMDEVICES] = { TW_DEVICE_ID, TW_DEVICE_ID2 };
dprintk(KERN_NOTICE "3w-xxxx: tw_findcards()\n");
for (i=0;i<TW_NUMDEVICES;i++) {
while ((tw_pci_dev = pci_find_device(TW_VENDOR_ID, device[i], tw_pci_dev))) {
j++;
if (pci_enable_device(tw_pci_dev))
continue;
/* We only need 32-bit addressing for 5,6,7xxx cards */
if (pci_set_dma_mask(tw_pci_dev, 0xffffffff)) {
printk(KERN_WARNING "3w-xxxx: No suitable DMA available.\n");
continue;
}
/* Prepare temporary device extension */
tw_dev=(TW_Device_Extension *)kmalloc(sizeof(TW_Device_Extension), GFP_ATOMIC);
if (tw_dev == NULL) {
printk(KERN_WARNING "3w-xxxx: tw_findcards(): kmalloc() failed for card %d.\n", j);
continue;
}
memset(tw_dev, 0, sizeof(TW_Device_Extension));
init_MUTEX(&tw_dev->ioctl_sem);
/* Save pci_dev struct to device extension */
tw_dev->tw_pci_dev = tw_pci_dev;
error = tw_initialize_device_extension(tw_dev);
if (error) {
printk(KERN_WARNING "3w-xxxx: tw_findcards(): Couldn't initialize device extension for card %d.\n", j);
tw_free_device_extension(tw_dev);
kfree(tw_dev);
continue;
}
/* Calculate the cards register addresses */
tw_dev->registers.base_addr = pci_resource_start(tw_pci_dev, 0);
tw_dev->registers.control_reg_addr = pci_resource_start(tw_pci_dev, 0);
tw_dev->registers.status_reg_addr = pci_resource_start(tw_pci_dev, 0) + 0x4;
tw_dev->registers.command_que_addr = pci_resource_start(tw_pci_dev, 0) + 0x8;
tw_dev->registers.response_que_addr = pci_resource_start(tw_pci_dev, 0) + 0xC;
/* Check for errors and clear them */
status_reg_value = inl(tw_dev->registers.status_reg_addr);
if (TW_STATUS_ERRORS(status_reg_value))
tw_decode_bits(tw_dev, status_reg_value, 0);
/* Poll status register for 60 secs for 'Controller Ready' flag */
if (tw_poll_status(tw_dev, TW_STATUS_MICROCONTROLLER_READY, 60)) {
printk(KERN_WARNING "3w-xxxx: tw_findcards(): Microcontroller not ready for card %d.\n", j);
tw_free_device_extension(tw_dev);
kfree(tw_dev);
continue;
}
/* Disable interrupts on the card */
tw_disable_interrupts(tw_dev);
tries = 0;
while (tries < TW_MAX_RESET_TRIES) {
/* Do soft reset */
tw_soft_reset(tw_dev);
error = tw_aen_drain_queue(tw_dev);
if (error) {
printk(KERN_WARNING "3w-xxxx: AEN drain failed for card %d.\n", j);
tries++;
continue;
}
/* Check for controller errors */
if (tw_check_errors(tw_dev)) {
printk(KERN_WARNING "3w-xxxx: Controller errors found, retrying for card %d.\n", j);
tries++;
continue;
}
/* Now the controller is in a good state */
break;
}
if (tries >= TW_MAX_RESET_TRIES) {
printk(KERN_WARNING "3w-xxxx: Controller errors, card not responding, check all cabling for card %d.\n", j);
tw_free_device_extension(tw_dev);
kfree(tw_dev);
continue;
}
/* Reserve the io address space */
if (!request_region((tw_dev->tw_pci_dev->resource[0].start), TW_IO_ADDRESS_RANGE, TW_DEVICE_NAME)) {
printk(KERN_WARNING "3w-xxxx: tw_findcards(): Couldn't get io range 0x%lx-0x%lx for card %d.\n",
(tw_dev->tw_pci_dev->resource[0].start),
(tw_dev->tw_pci_dev->resource[0].start) +
TW_IO_ADDRESS_RANGE, j);
tw_free_device_extension(tw_dev);
kfree(tw_dev);
continue;
}
error = tw_initialize_units(tw_dev);
if (error) {
printk(KERN_WARNING "3w-xxxx: No valid units for for card %d.\n", j);
}
error = tw_initconnection(tw_dev, TW_INIT_MESSAGE_CREDITS);
if (error) {
printk(KERN_WARNING "3w-xxxx: Connection initialization failed for card %d.\n", j);
release_region((tw_dev->tw_pci_dev->resource[0].start), TW_IO_ADDRESS_RANGE);
tw_free_device_extension(tw_dev);
kfree(tw_dev);
continue;
}
/* Set card status as online */
tw_dev->online = 1;
/* Calculate max cmds per lun, and setup queues */
if (tw_dev->num_units > 0) {
/* Use SHT cmd_per_lun here */
tw_dev->free_head = TW_Q_START;
tw_dev->free_tail = TW_Q_START;
tw_dev->free_wrap = TW_Q_LENGTH - 1;
}
/* Register the card with the kernel SCSI layer */
host = scsi_register(tw_host, sizeof(TW_Device_Extension));
if (host == NULL) {
printk(KERN_WARNING "3w-xxxx: tw_findcards(): scsi_register() failed for card %d.\n", j);
release_region((tw_dev->tw_pci_dev->resource[0].start), TW_IO_ADDRESS_RANGE);
tw_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 = 16;
/* Set max sectors per io */
#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,4,7)
host->max_sectors = TW_MAX_SECTORS;
#endif
#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,5,0)
scsi_set_device(host, &tw_pci_dev->dev);
#elif LINUX_VERSION_CODE >= KERNEL_VERSION(2,4,4)
scsi_set_pci_device(host, tw_pci_dev);
#endif
status_reg_value = inl(tw_dev->registers.status_reg_addr);
printk(KERN_NOTICE "scsi%d : Found a 3ware Storage Controller at 0x%x, IRQ: %d, P-chip: %d.%d\n", host->host_no,
(u32)(tw_pci_dev->resource[0].start), tw_pci_dev->irq,
(status_reg_value & TW_STATUS_MAJOR_VERSION_MASK) >> 28,
(status_reg_value & TW_STATUS_MINOR_VERSION_MASK) >> 24);
if (host->hostdata) {
tw_dev2 = (TW_Device_Extension *)host->hostdata;
memcpy(tw_dev2, tw_dev, sizeof(TW_Device_Extension));
tw_device_extension_list[tw_device_extension_count] = tw_dev2;
numcards++;
tw_device_extension_count = numcards;
tw_dev2->host = host;
} else {
printk(KERN_WARNING "3w-xxxx: tw_findcards(): Bad scsi host data for card %d.\n", j);
scsi_unregister(host);
release_region((tw_dev->tw_pci_dev->resource[0].start), TW_IO_ADDRESS_RANGE);
tw_free_device_extension(tw_dev);
kfree(tw_dev);
continue;
}
/* Tell the firmware we support shutdown notification*/
error = tw_setfeature(tw_dev2, 2, 1, &c);
if (error) {
printk(KERN_WARNING "3w-xxxx: Unable to set features for card %d, old firmware or card.\n", j);
}
/* Now setup the interrupt handler */
error = tw_setup_irq(tw_dev2);
if (error) {
printk(KERN_WARNING "3w-xxxx: tw_findcards(): Error requesting irq for card %d.\n", j);
scsi_unregister(host);
release_region((tw_dev->tw_pci_dev->resource[0].start), TW_IO_ADDRESS_RANGE);
tw_free_device_extension(tw_dev);
kfree(tw_dev);
numcards--;
continue;
}
/* Re-enable interrupts on the card */
tw_enable_interrupts(tw_dev2);
/* Free the temporary device extension */
if (tw_dev)
kfree(tw_dev);
}
}
if (numcards == 0) {
printk(KERN_WARNING "3w-xxxx: No cards found.\n");
} else {
register_reboot_notifier(&tw_notifier);
if ((twe_major = register_chrdev (0, "twe", &tw_fops)) < 0) {
printk(KERN_WARNING "3w-xxxx: Unable to register \"twe\" character device, error = %d.\n", twe_major);
}
}
return numcards;
} /* End tw_findcards() */
/* This function will free up device extension resources */
void tw_free_device_extension(TW_Device_Extension *tw_dev)
{
int i;
dprintk(KERN_NOTICE "3w-xxxx: tw_free_device_extension()\n");
/* Free command packet and generic buffer memory */
for (i=0;i<TW_Q_LENGTH;i++) {
if (tw_dev->command_packet_virtual_address[i])
pci_free_consistent(tw_dev->tw_pci_dev, sizeof(TW_Sector), tw_dev->command_packet_virtual_address[i], tw_dev->command_packet_physical_address[i]);
if (tw_dev->alignment_virtual_address[i])
pci_free_consistent(tw_dev->tw_pci_dev, sizeof(TW_Sector), tw_dev->alignment_virtual_address[i], tw_dev->alignment_physical_address[i]);
}
} /* End tw_free_device_extension() */
/* Clean shutdown routine */
static int tw_halt(struct notifier_block *nb, ulong event, void *buf)
{
int i;
for (i=0;i<tw_device_extension_count;i++) {
if (tw_device_extension_list[i]->online == 1) {
printk(KERN_NOTICE "3w-xxxx: Shutting down card %d.\n", i);
tw_shutdown_device(tw_device_extension_list[i]);
tw_device_extension_list[i]->online = 0;
}
}
unregister_reboot_notifier(&tw_notifier);
return NOTIFY_OK;
} /* End tw_halt() */
/* This function will send an initconnection command to controller */
int tw_initconnection(TW_Device_Extension *tw_dev, int message_credits)
{
unsigned long command_que_value;
u32 command_que_addr;
u32 response_que_addr;
TW_Command *command_packet;
TW_Response_Queue response_queue;
int request_id = 0;
dprintk(KERN_NOTICE "3w-xxxx: tw_initconnection()\n");
command_que_addr = tw_dev->registers.command_que_addr;
response_que_addr = tw_dev->registers.response_que_addr;
/* Initialize InitConnection command packet */
if (tw_dev->command_packet_virtual_address[request_id] == NULL) {
printk(KERN_WARNING "3w-xxxx: tw_initconnection(): 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->byte0.opcode = TW_OP_INIT_CONNECTION;
command_packet->byte0.sgl_offset = 0x0;
command_packet->size = TW_INIT_COMMAND_PACKET_SIZE;
command_packet->request_id = request_id;
command_packet->byte3.unit = 0x0;
command_packet->byte3.host_id = 0x0;
command_packet->status = 0x0;
command_packet->flags = 0x0;
command_packet->byte6.message_credits = message_credits;
command_packet->byte8.init_connection.response_queue_pointer = 0x0;
command_que_value = tw_dev->command_packet_physical_address[request_id];
if (command_que_value == 0) {
printk(KERN_WARNING "3w-xxxx: tw_initconnection(): Bad command packet physical address.\n");
return 1;
}
/* Send command packet to the board */
outl(command_que_value, command_que_addr);
/* Poll for completion */
if (tw_poll_status_gone(tw_dev, TW_STATUS_RESPONSE_QUEUE_EMPTY, 30) == 0) {
response_queue.value = inl(response_que_addr);
request_id = (unsigned char)response_queue.u.response_id;
if (request_id != 0) {
/* unexpected request id */
printk(KERN_WARNING "3w-xxxx: tw_initconnection(): Unexpected request id.\n");
return 1;
}
if (command_packet->status != 0) {
/* bad response */
tw_decode_sense(tw_dev, request_id, 0);
return 1;
}
}
return 0;
} /* End tw_initconnection() */
/* This function will initialize the fields of a device extension */
int tw_initialize_device_extension(TW_Device_Extension *tw_dev)
{
int i, error=0;
dprintk(KERN_NOTICE "3w-xxxx: tw_initialize_device_extension()\n");
/* Initialize command packet buffers */
error = tw_allocate_memory(tw_dev, sizeof(TW_Command), 0);
if (error) {
printk(KERN_WARNING "3w-xxxx: Command packet memory allocation failed.\n");
return 1;
}
/* Initialize generic buffer */
error = tw_allocate_memory(tw_dev, sizeof(TW_Sector), 1);
if (error) {
printk(KERN_WARNING "3w-xxxx: Generic memory allocation failed.\n");
return 1;
}
for (i=0;i<TW_Q_LENGTH;i++) {
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;
spin_lock_init(&tw_dev->tw_lock);
tw_dev->chrdev_request_id = TW_IOCTL_CHRDEV_FREE;
init_waitqueue_head(&tw_dev->ioctl_wqueue);
return 0;
} /* End tw_initialize_device_extension() */
/* This function will get unit info from the controller */
int tw_initialize_units(TW_Device_Extension *tw_dev)
{
int found = 0;
unsigned char request_id = 0;
TW_Command *command_packet;
TW_Param *param;
int i, imax, num_units = 0;
unsigned long command_que_value;
u32 command_que_addr;
u32 response_que_addr;
TW_Response_Queue response_queue;
unsigned long param_value;
unsigned char *is_unit_present;
dprintk(KERN_NOTICE "3w-xxxx: tw_initialize_units()\n");
command_que_addr = tw_dev->registers.command_que_addr;
response_que_addr = tw_dev->registers.response_que_addr;
/* Setup the command packet */
command_packet = (TW_Command *)tw_dev->command_packet_virtual_address[request_id];
if (command_packet == NULL) {
printk(KERN_WARNING "3w-xxxx: tw_initialize_units(): Bad command packet virtual address.\n");
return 1;
}
memset(command_packet, 0, sizeof(TW_Sector));
command_packet->byte0.opcode = TW_OP_GET_PARAM;
command_packet->byte0.sgl_offset = 2;
command_packet->size = 4;
command_packet->request_id = request_id;
command_packet->byte3.unit = 0;
command_packet->byte3.host_id = 0;
command_packet->status = 0;
command_packet->flags = 0;
command_packet->byte6.block_count = 1;
/* Now setup the param */
if (tw_dev->alignment_virtual_address[request_id] == NULL) {
printk(KERN_WARNING "3w-xxxx: tw_initialize_units(): 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 = 3; /* unit summary table */
param->parameter_id = 3; /* unitstatus parameter */
param->parameter_size_bytes = TW_MAX_UNITS;
param_value = tw_dev->alignment_physical_address[request_id];
if (param_value == 0) {
printk(KERN_WARNING "3w-xxxx: tw_initialize_units(): 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);
/* Post the command packet to the board */
command_que_value = tw_dev->command_packet_physical_address[request_id];
if (command_que_value == 0) {
printk(KERN_WARNING "3w-xxxx: tw_initialize_units(): Bad command packet physical address.\n");
return 1;
}
outl(command_que_value, command_que_addr);
/* Poll for completion */
if (tw_poll_status_gone(tw_dev, TW_STATUS_RESPONSE_QUEUE_EMPTY, 30) == 0) {
response_queue.value = inl(response_que_addr);
request_id = (unsigned char)response_queue.u.response_id;
if (request_id != 0) {
/* unexpected request id */
printk(KERN_WARNING "3w-xxxx: tw_initialize_units(): Unexpected request id.\n");
return 1;
}
if (command_packet->status != 0) {
/* bad response */
tw_decode_sense(tw_dev, request_id, 0);
return 1;
}
found = 1;
}
if (found == 0) {
/* response never received */
printk(KERN_WARNING "3w-xxxx: tw_initialize_units(): No response.\n");
return 1;
}
param = (TW_Param *)tw_dev->alignment_virtual_address[request_id];
is_unit_present = (unsigned char *)&(param->data[0]);
/* Show all units present */
imax = TW_MAX_UNITS;
for(i=0; i<imax; i++) {
if (is_unit_present[i] == 0) {
tw_dev->is_unit_present[i] = FALSE;
} else {
if (is_unit_present[i] & TW_UNIT_ONLINE) {
dprintk(KERN_NOTICE "3w-xxxx: tw_initialize_units(): Unit %d found.\n", i);
tw_dev->is_unit_present[i] = TRUE;
num_units++;
}
}
}
tw_dev->num_units = num_units;
if (num_units == 0) {
dprintk(KERN_NOTICE "3w-xxxx: tw_initialize_units(): No units found.\n");
return 1;
}
return 0;
} /* End tw_initialize_units() */
/* This function is the interrupt service routine */
static void tw_interrupt(int irq, void *dev_instance, struct pt_regs *regs)
{
int request_id;
u32 status_reg_addr, status_reg_value;
u32 response_que_addr;
TW_Device_Extension *tw_dev = (TW_Device_Extension *)dev_instance;
TW_Response_Queue response_que;
int error = 0, retval = 0;
unsigned long flags = 0;
TW_Command *command_packet;
dprintk(KERN_WARNING "3w-xxxx: tw_interrupt()\n");
/* See if we are already running on another processor */
if (test_and_set_bit(TW_IN_INTR, &tw_dev->flags))
return;
/* Get the host lock for io completions */
spin_lock_irqsave(tw_dev->host->host_lock, flags);
/* See if the interrupt matches this instance */
if (tw_dev->tw_pci_dev->irq == irq) {
/* Make sure io isn't queueing */
spin_lock(&tw_dev->tw_lock);
/* Read the registers */
status_reg_addr = tw_dev->registers.status_reg_addr;
response_que_addr = tw_dev->registers.response_que_addr;
status_reg_value = inl(status_reg_addr);
/* Check if this is our interrupt, otherwise bail */
if (!(status_reg_value & TW_STATUS_VALID_INTERRUPT))
goto tw_interrupt_bail;
/* Check controller for errors */
if (tw_check_bits(status_reg_value)) {
dprintk(KERN_WARNING "3w-xxxx: tw_interrupt(): Unexpected bits.\n");
if (tw_decode_bits(tw_dev, status_reg_value, 1)) {
tw_clear_all_interrupts(tw_dev);
goto tw_interrupt_bail;
}
}
/* Handle host interrupt */
if (status_reg_value & TW_STATUS_HOST_INTERRUPT) {
dprintk(KERN_NOTICE "3w-xxxx: tw_interrupt(): Received host interrupt.\n");
tw_clear_host_interrupt(tw_dev);
}
/* Handle attention interrupt */
if (status_reg_value & TW_STATUS_ATTENTION_INTERRUPT) {
dprintk(KERN_NOTICE "3w-xxxx: tw_interrupt(): Received attention interrupt.\n");
tw_clear_attention_interrupt(tw_dev);
tw_state_request_start(tw_dev, &request_id);
error = tw_aen_read_queue(tw_dev, request_id);
if (error) {
printk(KERN_WARNING "3w-xxxx: scsi%d: Error reading aen queue.\n", tw_dev->host->host_no);
tw_dev->state[request_id] = TW_S_COMPLETED;
tw_state_request_finish(tw_dev, request_id);
}
}
/* Handle command interrupt */
if (status_reg_value & TW_STATUS_COMMAND_INTERRUPT) {
/* 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) {
printk(KERN_WARNING "3w-xxxx: scsi%d: Found request id that wasn't pending.\n", tw_dev->host->host_no);
break;
}
if (tw_post_command_packet(tw_dev, request_id)==0) {
if (tw_dev->pending_head == TW_Q_LENGTH-1) {
tw_dev->pending_head = TW_Q_START;
} else {
tw_dev->pending_head = tw_dev->pending_head + 1;
}
tw_dev->pending_request_count--;
} else {
/* If we get here, we will continue re-posting on the next command interrupt */
break;
}
}
/* If there are no more pending requests, we mask command interrupt */
if (tw_dev->pending_request_count == 0)
tw_mask_command_interrupt(tw_dev);
}
/* 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) {
/* Read response queue register */
response_que.value = inl(response_que_addr);
request_id = response_que.u.response_id;
command_packet = (TW_Command *)tw_dev->command_packet_virtual_address[request_id];
error = 0;
/* Check for bad response */
if (command_packet->status != 0) {
/* If internal command, don't error, don't fill sense */
if (tw_dev->srb[request_id] == 0) {
tw_decode_sense(tw_dev, request_id, 0);
} else {
error = tw_decode_sense(tw_dev, request_id, 1);
}
}
/* Check for correct state */
if (tw_dev->state[request_id] != TW_S_POSTED) {
/* Handle timed out ioctl's */
if (tw_dev->srb[request_id] != 0) {
if (tw_dev->srb[request_id]->cmnd[0] != TW_IOCTL) {
printk(KERN_WARNING "3w-xxxx: scsi%d: Received a request id (%d) (opcode = 0x%x) that wasn't posted.\n", tw_dev->host->host_no, request_id, command_packet->byte0.opcode);
error = 1;
}
}
}
dprintk(KERN_NOTICE "3w-xxxx: tw_interrupt(): Response queue request id: %d.\n", request_id);
/* Check for internal command completion */
if (tw_dev->srb[request_id] == 0) {
dprintk(KERN_WARNING "3w-xxxx: tw_interrupt(): Found internally posted command.\n");
/* Check for chrdev ioctl completion */
if (request_id != tw_dev->chrdev_request_id) {
retval = tw_aen_complete(tw_dev, request_id);
if (retval) {
printk(KERN_WARNING "3w-xxxx: scsi%d: Error completing aen.\n", tw_dev->host->host_no);
}
} else {
tw_dev->chrdev_request_id = TW_IOCTL_CHRDEV_FREE;
}
} else {
switch (tw_dev->srb[request_id]->cmnd[0]) {
case READ_10:
case READ_6:
dprintk(KERN_NOTICE "3w-xxxx: tw_interrupt(): caught READ_10/READ_6\n");
break;
case WRITE_10:
case WRITE_6:
dprintk(KERN_NOTICE "3w-xxxx: tw_interrupt(): caught WRITE_10/WRITE_6\n");
break;
case TEST_UNIT_READY:
dprintk(KERN_NOTICE "3w-xxxx: tw_interrupt(): caught TEST_UNIT_READY\n");
error = tw_scsiop_test_unit_ready_complete(tw_dev, request_id);
break;
case INQUIRY:
dprintk(KERN_NOTICE "3w-xxxx: tw_interrupt(): caught INQUIRY\n");
error = tw_scsiop_inquiry_complete(tw_dev, request_id);
break;
case READ_CAPACITY:
dprintk(KERN_NOTICE "3w-xxxx: tw_interrupt(): caught READ_CAPACITY\n");
error = tw_scsiop_read_capacity_complete(tw_dev, request_id);
break;
case MODE_SENSE:
dprintk(KERN_NOTICE "3w-xxxx: tw_interrupt(): caught MODE_SENSE\n");
error = tw_scsiop_mode_sense_complete(tw_dev, request_id);
break;
case SYNCHRONIZE_CACHE:
dprintk(KERN_NOTICE "3w-xxxx: tw_interrupt(): caught SYNCHRONIZE_CACHE\n");
break;
case TW_IOCTL:
dprintk(KERN_NOTICE "3w-xxxx: tw_interrupt(): caught TW_IOCTL\n");
error = tw_ioctl_complete(tw_dev, request_id);
break;
default:
printk(KERN_WARNING "3w-xxxx: case slip in tw_interrupt()\n");
error = 1;
}
/* 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 */
if ((error != TW_ISR_DONT_COMPLETE)) {
tw_dev->state[request_id] = TW_S_COMPLETED;
tw_state_request_finish(tw_dev, request_id);
tw_dev->posted_request_count--;
tw_dev->srb[request_id]->scsi_done(tw_dev->srb[request_id]);
tw_unmap_scsi_data(tw_dev->tw_pci_dev, tw_dev->srb[request_id]);
}
}
/* Check for valid status after each drain */
status_reg_value = inl(status_reg_addr);
if (tw_check_bits(status_reg_value)) {
dprintk(KERN_WARNING "3w-xxxx: tw_interrupt(): Unexpected bits.\n");
if (tw_decode_bits(tw_dev, status_reg_value, 1)) {
tw_clear_all_interrupts(tw_dev);
goto tw_interrupt_bail;
}
}
}
}
tw_interrupt_bail:
spin_unlock(&tw_dev->tw_lock);
} else
dprintk(KERN_WARNING "3w-xxxx: tw_interrupt() called for wrong instance.\n");
spin_unlock_irqrestore(tw_dev->host->host_lock, flags);
clear_bit(TW_IN_INTR, &tw_dev->flags);
} /* End tw_interrupt() */
/* This function handles ioctls from userspace to the driver */
int tw_ioctl(TW_Device_Extension *tw_dev, int request_id)
{
unsigned char opcode;
int bufflen, error = 0;
TW_Param *param;
TW_Command *command_packet, *command_save;
unsigned long param_value;
TW_Ioctl *ioctl = NULL;
TW_Passthru *passthru = NULL;
int tw_aen_code, i, use_sg;
unsigned long *data_ptr;
int total_bytes = 0, posted = 0;
dma_addr_t dma_handle;
struct timeval before, timeout;
ioctl = (TW_Ioctl *)tw_dev->srb[request_id]->request_buffer;
if (ioctl == NULL) {
printk(KERN_WARNING "3w-xxxx: tw_ioctl(): Request buffer NULL.\n");
tw_dev->state[request_id] = TW_S_COMPLETED;
tw_state_request_finish(tw_dev, request_id);
tw_dev->srb[request_id]->result = (DID_OK << 16);
tw_dev->srb[request_id]->scsi_done(tw_dev->srb[request_id]);
return 0;
}
bufflen = tw_dev->srb[request_id]->request_bufflen;
/* Initialize command packet */
command_packet = (TW_Command *)tw_dev->command_packet_virtual_address[request_id];
if (command_packet == NULL) {
printk(KERN_WARNING "3w-xxxx: tw_ioctl(): Bad command packet virtual address.\n");
tw_dev->state[request_id] = TW_S_COMPLETED;
tw_state_request_finish(tw_dev, request_id);
tw_dev->srb[request_id]->result = (DID_OK << 16);
tw_dev->srb[request_id]->scsi_done(tw_dev->srb[request_id]);
return 0;
}
memset(command_packet, 0, sizeof(TW_Sector));
/* Initialize param */
if (tw_dev->alignment_virtual_address[request_id] == NULL) {
printk(KERN_WARNING "3w-xxxx: tw_ioctl(): Bad alignment virtual address.\n");
tw_dev->state[request_id] = TW_S_COMPLETED;
tw_state_request_finish(tw_dev, request_id);
tw_dev->srb[request_id]->result = (DID_OK << 16);
tw_dev->srb[request_id]->scsi_done(tw_dev->srb[request_id]);
return 0;
}
param = (TW_Param *)tw_dev->alignment_virtual_address[request_id];
memset(param, 0, sizeof(TW_Sector));
dprintk(KERN_NOTICE "opcode = %d table_id = %d parameter_id = %d parameter_size_bytes = %d\n", ioctl->opcode, ioctl->table_id, ioctl->parameter_id, ioctl->parameter_size_bytes);
opcode = ioctl->opcode;
switch (opcode) {
case TW_OP_NOP:
dprintk(KERN_NOTICE "3w-xxxx: tw_ioctl(): caught TW_OP_NOP.\n");
command_packet->byte0.opcode = TW_OP_NOP;
break;
case TW_OP_GET_PARAM:
dprintk(KERN_NOTICE "3w-xxxx: tw_ioctl(): caught TW_OP_GET_PARAM.\n");
command_packet->byte0.opcode = TW_OP_GET_PARAM;
command_packet->byte3.unit = ioctl->unit_index;
param->table_id = ioctl->table_id;
param->parameter_id = ioctl->parameter_id;
param->parameter_size_bytes = ioctl->parameter_size_bytes;
tw_dev->ioctl_size[request_id] = ioctl->parameter_size_bytes;
dprintk(KERN_NOTICE "table_id = %d parameter_id = %d parameter_size_bytes %d\n", param->table_id, param->parameter_id, param->parameter_size_bytes);
break;
case TW_OP_SET_PARAM:
dprintk(KERN_NOTICE "3w-xxxx: tw_ioctl(): caught TW_OP_SET_PARAM: table_id = %d, parameter_id = %d, parameter_size_bytes = %d.\n",
ioctl->table_id, ioctl->parameter_id, ioctl->parameter_size_bytes);
if (ioctl->data != NULL) {
command_packet->byte0.opcode = TW_OP_SET_PARAM;
param->table_id = ioctl->table_id;
param->parameter_id = ioctl->parameter_id;
param->parameter_size_bytes = ioctl->parameter_size_bytes;
memcpy(param->data, ioctl->data, ioctl->parameter_size_bytes);
break;
} else {
printk(KERN_WARNING "3w-xxxx: tw_ioctl(): ioctl->data NULL.\n");
return 1;
}
case TW_OP_AEN_LISTEN:
dprintk(KERN_NOTICE "3w-xxxx: tw_ioctl(): caught TW_OP_AEN_LISTEN.\n");
if (tw_dev->aen_head == tw_dev->aen_tail) {
/* aen queue empty */
dprintk(KERN_NOTICE "3w-xxxx: tw_ioctl(): Aen queue empty.\n");
tw_aen_code = TW_AEN_QUEUE_EMPTY;
memcpy(tw_dev->srb[request_id]->request_buffer, &tw_aen_code, ioctl->parameter_size_bytes);
} else {
/* Copy aen queue entry to request buffer */
dprintk(KERN_NOTICE "3w-xxxx: tw_ioctl(): Returning aen 0x%x\n", tw_dev->aen_queue[tw_dev->aen_head]);
tw_aen_code = tw_dev->aen_queue[tw_dev->aen_head];
memcpy(tw_dev->srb[request_id]->request_buffer, &tw_aen_code, ioctl->parameter_size_bytes);
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;
}
}
tw_dev->state[request_id] = TW_S_COMPLETED;
tw_state_request_finish(tw_dev, request_id);
tw_dev->srb[request_id]->result = (DID_OK << 16);
tw_dev->srb[request_id]->scsi_done(tw_dev->srb[request_id]);
return 0;
case TW_ATA_PASSTHRU:
if (ioctl->data != NULL) {
memcpy(command_packet, ioctl->data, sizeof(TW_Command));
command_packet->request_id = request_id;
} else {
printk(KERN_WARNING "3w-xxxx: tw_ioctl(): ioctl->data NULL.\n");
return 1;
}
passthru = (TW_Passthru *)tw_dev->command_packet_virtual_address[request_id];
passthru->sg_list[0].length = passthru->sector_count*512;
if (passthru->sg_list[0].length > TW_MAX_PASSTHRU_BYTES) {
printk(KERN_WARNING "3w-xxxx: tw_ioctl(): Passthru size (%d) too big.\n", passthru->sg_list[0].length);
return 1;
}
passthru->sg_list[0].address = tw_dev->alignment_physical_address[request_id];
tw_post_command_packet(tw_dev, request_id);
return 0;
case TW_CMD_PACKET:
dprintk(KERN_WARNING "3w-xxxx: tw_ioctl(): caught TW_CMD_PACKET.\n");
if (ioctl->data != NULL) {
memcpy(command_packet, ioctl->data, sizeof(TW_Command));
command_packet->request_id = request_id;
tw_post_command_packet(tw_dev, request_id);
return 0;
} else {
printk(KERN_WARNING "3w-xxxx: tw_ioctl(): ioctl->data NULL.\n");
return 1;
}
case TW_CMD_PACKET_WITH_DATA:
dprintk(KERN_WARNING "3w-xxxx: tw_ioctl(): caught TW_CMD_PACKET_WITH_DATA.\n");
command_save = (TW_Command *)tw_dev->alignment_virtual_address[request_id];
if (command_save == NULL) {
printk(KERN_WARNING "3w-xxxx: scsi%d: tw_ioctl(): Bad alignment virtual address.\n", tw_dev->host->host_no);
return 1;
}
if (ioctl->data != NULL) {
/* Copy down the command packet */
memcpy(command_packet, ioctl->data, sizeof(TW_Command));
memcpy(command_save, ioctl->data, sizeof(TW_Command));
command_packet->request_id = request_id;
/* Now deal with the two possible sglists */
if (command_packet->byte0.sgl_offset == 2) {
use_sg = command_packet->size - 3;
for (i=0;i<use_sg;i++)
total_bytes+=command_packet->byte8.param.sgl[i].length;
tw_dev->ioctl_data[request_id] = pci_alloc_consistent(tw_dev->tw_pci_dev, total_bytes, &dma_handle);
if (!tw_dev->ioctl_data[request_id]) {
printk(KERN_WARNING "3w-xxxx: scsi%d: tw_ioctl(): pci_alloc_consistent() failed for request_id %d.\n", tw_dev->host->host_no, request_id);
return 1;
}
/* Copy param sglist into the kernel */
data_ptr = tw_dev->ioctl_data[request_id];
for (i=0;i<use_sg;i++) {
if (command_packet->byte8.param.sgl[i].address != 0) {
error = copy_from_user(data_ptr, (void *)(unsigned long)command_packet->byte8.param.sgl[i].address, command_packet->byte8.param.sgl[i].length);
if (error) {
dprintk(KERN_WARNING "3w-xxxx: scsi%d: Error copying param sglist from userspace.\n", tw_dev->host->host_no);
goto tw_ioctl_bail;
}
} else {
printk(KERN_WARNING "3w-xxxx: scsi%d: tw_ioctl(): Bad param sgl address.\n", tw_dev->host->host_no);
tw_dev->srb[request_id]->result = (DID_RESET << 16);
goto tw_ioctl_bail;
}
data_ptr+=command_packet->byte8.param.sgl[i].length;
}
command_packet->size = 4;
command_packet->byte8.param.sgl[0].address = dma_handle;
command_packet->byte8.param.sgl[0].length = total_bytes;
}
if (command_packet->byte0.sgl_offset == 3) {
use_sg = command_packet->size - 4;
for (i=0;i<use_sg;i++)
total_bytes+=command_packet->byte8.io.sgl[i].length;
tw_dev->ioctl_data[request_id] = pci_alloc_consistent(tw_dev->tw_pci_dev, total_bytes, &dma_handle);
if (!tw_dev->ioctl_data[request_id]) {
printk(KERN_WARNING "3w-xxxx: scsi%d: tw_ioctl(): pci_alloc_consistent() failed for request_id %d.\n", tw_dev->host->host_no, request_id);
return 1;
}
if (command_packet->byte0.opcode == TW_OP_WRITE) {
/* Copy io sglist into the kernel */
data_ptr = tw_dev->ioctl_data[request_id];
for (i=0;i<use_sg;i++) {
if (command_packet->byte8.io.sgl[i].address != 0) {
error = copy_from_user(data_ptr, (void *)(unsigned long)command_packet->byte8.io.sgl[i].address, command_packet->byte8.io.sgl[i].length);
if (error) {
dprintk(KERN_WARNING "3w-xxxx: scsi%d: Error copying io sglist from userspace.\n", tw_dev->host->host_no);
goto tw_ioctl_bail;
}
} else {
printk(KERN_WARNING "3w-xxxx: scsi%d: tw_ioctl(): Bad io sgl address.\n", tw_dev->host->host_no);
tw_dev->srb[request_id]->result = (DID_RESET << 16);
goto tw_ioctl_bail;
}
data_ptr+=command_packet->byte8.io.sgl[i].length;
}
}
command_packet->size = 5;
command_packet->byte8.io.sgl[0].address = dma_handle;
command_packet->byte8.io.sgl[0].length = total_bytes;
}
spin_unlock(&tw_dev->tw_lock);
spin_unlock_irq(tw_dev->host->host_lock);
set_bit(TW_IN_IOCTL, &tw_dev->flags);
/* Finally post the command packet */
tw_post_command_packet(tw_dev, request_id);
posted = 1;
do_gettimeofday(&before);
tw_ioctl_retry:
mdelay(TW_IOCTL_WAIT_TIME);
if (test_bit(TW_IN_IOCTL, &tw_dev->flags)) {
do_gettimeofday(&timeout);
if (before.tv_sec + TW_IOCTL_TIMEOUT < timeout.tv_sec) {
spin_lock_irq(tw_dev->host->host_lock);
spin_lock(&tw_dev->tw_lock);
goto tw_ioctl_bail;
} else {
goto tw_ioctl_retry;
}
}
spin_lock_irq(tw_dev->host->host_lock);
spin_lock(&tw_dev->tw_lock);
if (signal_pending(current)) {
dprintk(KERN_WARNING "3w-xxxx: scsi%d: tw_ioctl(): Signal pending, aborting ioctl().\n", tw_dev->host->host_no);
tw_dev->srb[request_id]->result = (DID_OK << 16);
goto tw_ioctl_bail;
}
tw_dev->srb[request_id]->result = (DID_OK << 16);
/* Now copy up the param or io sglist to userspace */
if (command_packet->byte0.sgl_offset == 2) {
use_sg = command_save->size - 3;
data_ptr = tw_dev->ioctl_data[request_id];
for (i=0;i<use_sg;i++) {
if (command_save->byte8.param.sgl[i].address != 0) {
error = copy_to_user((void *)(unsigned long)command_save->byte8.param.sgl[i].address, data_ptr, command_save->byte8.param.sgl[i].length);
if (error) {
dprintk(KERN_WARNING "3w-xxxx: scsi%d: Error copying param sglist to userspace.\n", tw_dev->host->host_no);
goto tw_ioctl_bail;
}
dprintk(KERN_WARNING "3w-xxxx: scsi%d: Copied %ld bytes to pid %d.\n", tw_dev->host->host_no, command_save->byte8.param.sgl[i].length, current->pid);
data_ptr+=command_save->byte8.param.sgl[i].length;
} else {
printk(KERN_WARNING "3w-xxxx: scsi%d: tw_ioctl(): Bad param sgl address.\n", tw_dev->host->host_no);
tw_dev->srb[request_id]->result = (DID_RESET << 16);
goto tw_ioctl_bail;
}
}
}
if (command_packet->byte0.sgl_offset == 3) {
use_sg = command_save->size - 4;
if (command_packet->byte0.opcode == TW_OP_READ) {
data_ptr = tw_dev->ioctl_data[request_id];
for(i=0;i<use_sg;i++) {
if (command_save->byte8.io.sgl[i].address != 0) {
error = copy_to_user((void *)(unsigned long)command_save->byte8.io.sgl[i].address, data_ptr, command_save->byte8.io.sgl[i].length);
if (error) {
dprintk(KERN_WARNING "3w-xxxx: scsi%d: Error copying io sglist to userspace.\n", tw_dev->host->host_no);
goto tw_ioctl_bail;
}
dprintk(KERN_WARNING "3w-xxxx: scsi%d: Copied %ld bytes to pid %d.\n", tw_dev->host->host_no, command_save->byte8.io.sgl[i].length, current->pid);
data_ptr+=command_save->byte8.io.sgl[i].length;
} else {
printk(KERN_WARNING "3w-xxxx: scsi%d: tw_ioctl(): Bad io sgl address.\n", tw_dev->host->host_no);
tw_dev->srb[request_id]->result = (DID_RESET << 16);
goto tw_ioctl_bail;
}
}
}
}
tw_ioctl_bail:
/* Free up sglist memory */
if (tw_dev->ioctl_data[request_id])
pci_free_consistent(tw_dev->tw_pci_dev, total_bytes, tw_dev->ioctl_data[request_id], dma_handle);
else
printk(KERN_WARNING "3w-xxxx: scsi%d: tw_ioctl(): Error freeing ioctl data.\n", tw_dev->host->host_no);
/* Now complete the io */
tw_dev->state[request_id] = TW_S_COMPLETED;
tw_state_request_finish(tw_dev, request_id);
if (posted)
tw_dev->posted_request_count--;
tw_dev->srb[request_id]->scsi_done(tw_dev->srb[request_id]);
return 0;
} else {
printk(KERN_WARNING "3w-xxxx: tw_ioctl(): ioctl->data NULL.\n");
return 1;
}
default:
dprintk(KERN_WARNING "3w-xxxx: Unknown ioctl 0x%x.\n", opcode);
tw_dev->state[request_id] = TW_S_COMPLETED;
tw_state_request_finish(tw_dev, request_id);
tw_dev->srb[request_id]->result = (DID_OK << 16);
tw_dev->srb[request_id]->scsi_done(tw_dev->srb[request_id]);
return 0;
}
param_value = tw_dev->alignment_physical_address[request_id];
if (param_value == 0) {
printk(KERN_WARNING "3w-xxxx: tw_ioctl(): Bad alignment physical address.\n");
tw_dev->state[request_id] = TW_S_COMPLETED;
tw_state_request_finish(tw_dev, request_id);
tw_dev->srb[request_id]->result = (DID_OK << 16);
tw_dev->srb[request_id]->scsi_done(tw_dev->srb[request_id]);
}
command_packet->byte8.param.sgl[0].address = param_value;
command_packet->byte8.param.sgl[0].length = sizeof(TW_Sector);
command_packet->byte0.sgl_offset = 2;
command_packet->size = 4;
command_packet->request_id = request_id;
command_packet->byte3.host_id = 0;
command_packet->status = 0;
command_packet->flags = 0;
command_packet->byte6.parameter_count = 1;
/* Now try to post the command to the board */
tw_post_command_packet(tw_dev, request_id);
return 0;
} /* End tw_ioctl() */
/* This function is called by the isr to complete ioctl requests */
int tw_ioctl_complete(TW_Device_Extension *tw_dev, int request_id)
{
unsigned char *param_data;
unsigned char *buff;
TW_Param *param;
TW_Ioctl *ioctl = NULL;
TW_Passthru *passthru = NULL;
TW_Command *command_packet;
ioctl = (TW_Ioctl *)tw_dev->srb[request_id]->request_buffer;
dprintk(KERN_NOTICE "3w-xxxx: tw_ioctl_complete()\n");
buff = tw_dev->srb[request_id]->request_buffer;
if (buff == NULL) {
printk(KERN_WARNING "3w-xxxx: tw_ioctl_complete(): Request buffer NULL.\n");
return 1;
}
command_packet = (TW_Command *)tw_dev->command_packet_virtual_address[request_id];
if (command_packet == NULL) {
printk(KERN_WARNING "3w-xxxx: scsi%d: tw_ioctl_complete(): Bad command packet virtual address.\n", tw_dev->host->host_no);
return 1;
}
dprintk(KERN_NOTICE "3w-xxxx: tw_ioctl_complete(): Request_bufflen = %d\n", tw_dev->srb[request_id]->request_bufflen);
ioctl = (TW_Ioctl *)buff;
switch (ioctl->opcode) {
case TW_ATA_PASSTHRU:
passthru = (TW_Passthru *)ioctl->data;
memcpy(buff, tw_dev->alignment_virtual_address[request_id], passthru->sector_count * 512);
break;
case TW_CMD_PACKET_WITH_DATA:
dprintk(KERN_WARNING "3w-xxxx: tw_ioctl_complete(): caught TW_CMD_PACKET_WITH_DATA.\n");
clear_bit(TW_IN_IOCTL, &tw_dev->flags);
return TW_ISR_DONT_COMPLETE; /* Special case for isr to not complete io */
default:
memset(buff, 0, tw_dev->srb[request_id]->request_bufflen);
param = (TW_Param *)tw_dev->alignment_virtual_address[request_id];
if (param == NULL) {
printk(KERN_WARNING "3w-xxxx: tw_ioctl_complete(): Bad alignment virtual address.\n");
return 1;
}
param_data = &(param->data[0]);
memcpy(buff, param_data, tw_dev->ioctl_size[request_id]);
}
return 0;
} /* End tw_ioctl_complete() */
static int tw_map_scsi_sg_data(struct pci_dev *pdev, Scsi_Cmnd *cmd)
{
int use_sg;
int dma_dir = scsi_to_pci_dma_dir(cmd->sc_data_direction);
dprintk(KERN_WARNING "3w-xxxx: tw_map_scsi_sg_data()\n");
if (cmd->use_sg == 0)
return 0;
use_sg = pci_map_sg(pdev, cmd->buffer, cmd->use_sg, dma_dir);
if (use_sg == 0) {
printk(KERN_WARNING "3w-xxxx: tw_map_scsi_sg_data(): pci_map_sg() failed.\n");
return 0;
}
cmd->SCp.phase = 2;
cmd->SCp.have_data_in = use_sg;
return use_sg;
} /* End tw_map_scsi_sg_data() */
static u32 tw_map_scsi_single_data(struct pci_dev *pdev, Scsi_Cmnd *cmd)
{
dma_addr_t mapping;
int dma_dir = scsi_to_pci_dma_dir(cmd->sc_data_direction);
dprintk(KERN_WARNING "3w-xxxx: tw_map_scsi_single_data()\n");
if (cmd->request_bufflen == 0)
return 0;
mapping = pci_map_page(pdev, virt_to_page(cmd->request_buffer), ((unsigned long)cmd->request_buffer & ~PAGE_MASK), cmd->request_bufflen, dma_dir);
if (mapping == 0) {
printk(KERN_WARNING "3w-xxxx: tw_map_scsi_single_data(): pci_map_page() failed.\n");
return 0;
}
cmd->SCp.phase = 1;
cmd->SCp.have_data_in = mapping;
return mapping;
} /* End tw_map_scsi_single_data() */
/* This function will mask the command interrupt */
void tw_mask_command_interrupt(TW_Device_Extension *tw_dev)
{
u32 control_reg_addr, control_reg_value;
control_reg_addr = tw_dev->registers.control_reg_addr;
control_reg_value = TW_CONTROL_MASK_COMMAND_INTERRUPT;
outl(control_reg_value, control_reg_addr);
} /* End tw_mask_command_interrupt() */
/* This function will poll the status register for a flag */
int tw_poll_status(TW_Device_Extension *tw_dev, u32 flag, int seconds)
{
u32 status_reg_addr, status_reg_value;
struct timeval before, timeout;
status_reg_addr = tw_dev->registers.status_reg_addr;
do_gettimeofday(&before);
status_reg_value = inl(status_reg_addr);
if (tw_check_bits(status_reg_value)) {
dprintk(KERN_WARNING "3w-xxxx: tw_poll_status(): Unexpected bits.\n");
tw_decode_bits(tw_dev, status_reg_value, 0);
}
while ((status_reg_value & flag) != flag) {
status_reg_value = inl(status_reg_addr);
if (tw_check_bits(status_reg_value)) {
dprintk(KERN_WARNING "3w-xxxx: tw_poll_status(): Unexpected bits.\n");
tw_decode_bits(tw_dev, status_reg_value, 0);
}
do_gettimeofday(&timeout);
if (before.tv_sec + seconds < timeout.tv_sec) {
dprintk(KERN_WARNING "3w-xxxx: tw_poll_status(): Flag 0x%x not found.\n", flag);
return 1;
}
mdelay(5);
}
return 0;
} /* End tw_poll_status() */
/* This function will poll the status register for disappearance of a flag */
int tw_poll_status_gone(TW_Device_Extension *tw_dev, u32 flag, int seconds)
{
u32 status_reg_addr, status_reg_value;
struct timeval before, timeout;
status_reg_addr = tw_dev->registers.status_reg_addr;
do_gettimeofday(&before);
status_reg_value = inl(status_reg_addr);
if (tw_check_bits(status_reg_value)) {
dprintk(KERN_WARNING "3w-xxxx: tw_poll_status_gone(): Unexpected bits.\n");
tw_decode_bits(tw_dev, status_reg_value, 0);
}
while ((status_reg_value & flag) != 0) {
status_reg_value = inl(status_reg_addr);
if (tw_check_bits(status_reg_value)) {
dprintk(KERN_WARNING "3w-xxxx: tw_poll_status_gone(): Unexpected bits.\n");
tw_decode_bits(tw_dev, status_reg_value, 0);
}
do_gettimeofday(&timeout);
if (before.tv_sec + seconds < timeout.tv_sec) {
dprintk(KERN_WARNING "3w-xxxx: tw_poll_status_gone(): Flag 0x%x never disappeared.\n", flag);
return 1;
}
mdelay(5);
}
return 0;
} /* End tw_poll_status_gone() */
/* This function will attempt to post a command packet to the board */
int tw_post_command_packet(TW_Device_Extension *tw_dev, int request_id)
{
u32 status_reg_addr, status_reg_value;
unsigned long command_que_value;
u32 command_que_addr;
dprintk(KERN_NOTICE "3w-xxxx: tw_post_command_packet()\n");
command_que_addr = tw_dev->registers.command_que_addr;
command_que_value = tw_dev->command_packet_physical_address[request_id];
status_reg_addr = tw_dev->registers.status_reg_addr;
status_reg_value = inl(status_reg_addr);
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, command_que_addr);
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 reset a device extension */
int tw_reset_device_extension(TW_Device_Extension *tw_dev)
{
int imax = 0;
int i = 0;
Scsi_Cmnd *srb;
dprintk(KERN_NOTICE "3w-xxxx: tw_reset_device_extension()\n");
imax = TW_Q_LENGTH;
if (tw_reset_sequence(tw_dev)) {
printk(KERN_WARNING "3w-xxxx: scsi%d: Reset sequence failed.\n", tw_dev->host->host_no);
return 1;
}
/* Abort all requests that are in progress */
for (i=0;i<imax;i++) {
if ((tw_dev->state[i] != TW_S_FINISHED) &&
(tw_dev->state[i] != TW_S_INITIAL) &&
(tw_dev->state[i] != TW_S_COMPLETED)) {
srb = tw_dev->srb[i];
if (srb != NULL) {
srb->result = (DID_RESET << 16);
tw_dev->srb[i]->scsi_done(tw_dev->srb[i]);
tw_unmap_scsi_data(tw_dev->tw_pci_dev, tw_dev->srb[i]);
}
}
}
/* Reset queues and counts */
for (i=0;i<imax;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;
return 0;
} /* End tw_reset_device_extension() */
/* This function will reset a controller */
int tw_reset_sequence(TW_Device_Extension *tw_dev)
{
int error = 0;
int tries = 0;
/* Disable interrupts */
tw_disable_interrupts(tw_dev);
/* Reset the board */
while (tries < TW_MAX_RESET_TRIES) {
tw_soft_reset(tw_dev);
error = tw_aen_drain_queue(tw_dev);
if (error) {
printk(KERN_WARNING "3w-xxxx: scsi%d: AEN drain failed, retrying.\n", tw_dev->host->host_no);
tries++;
continue;
}
/* Check for controller errors */
if (tw_check_errors(tw_dev)) {
printk(KERN_WARNING "3w-xxxx: scsi%d: Controller errors found, retrying.\n", tw_dev->host->host_no);
tries++;
continue;
}
/* Now the controller is in a good state */
break;
}
if (tries >= TW_MAX_RESET_TRIES) {
printk(KERN_WARNING "3w-xxxx: scsi%d: Controller errors, card not responding, check all cabling.\n", tw_dev->host->host_no);
return 1;
}
error = tw_initconnection(tw_dev, TW_INIT_MESSAGE_CREDITS);
if (error) {
printk(KERN_WARNING "3w-xxxx: scsi%d: Connection initialization failed.\n", tw_dev->host->host_no);
return 1;
}
/* Re-enable interrupts */
tw_enable_and_clear_interrupts(tw_dev);
return 0;
} /* End tw_reset_sequence() */
/* This funciton returns unit geometry in cylinders/heads/sectors */
int tw_scsi_biosparam(struct scsi_device *sdev, struct block_device *bdev,
sector_t capacity, int geom[])
{
int heads, sectors, cylinders;
TW_Device_Extension *tw_dev;
dprintk(KERN_NOTICE "3w-xxxx: tw_scsi_biosparam()\n");
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);
}
dprintk(KERN_NOTICE "3w-xxxx: tw_scsi_biosparam(): heads = %d, sectors = %d, cylinders = %d\n", heads, sectors, cylinders);
geom[0] = heads;
geom[1] = sectors;
geom[2] = cylinders;
return 0;
} /* End tw_scsi_biosparam() */
/* This function will find and initialize any cards */
int tw_scsi_detect(Scsi_Host_Template *tw_host)
{
int ret;
dprintk(KERN_NOTICE "3w-xxxx: tw_scsi_detect()\n");
printk(KERN_WARNING "3ware Storage Controller device driver for Linux v%s.\n", tw_driver_version);
/* Check if the kernel has PCI interface compiled in */
if (!pci_present()) {
printk(KERN_WARNING "3w-xxxx: tw_scsi_detect(): No pci interface present.\n");
return 0;
}
ret = tw_findcards(tw_host);
return ret;
} /* End tw_scsi_detect() */
/* This is the new scsi eh abort function */
int tw_scsi_eh_abort(Scsi_Cmnd *SCpnt)
{
TW_Device_Extension *tw_dev=NULL;
int i = 0;
dprintk(KERN_NOTICE "3w-xxxx: tw_scsi_eh_abort()\n");
if (!SCpnt) {
printk(KERN_WARNING "3w-xxxx: tw_scsi_eh_abort(): Invalid Scsi_Cmnd.\n");
return (FAILED);
}
tw_dev = (TW_Device_Extension *)SCpnt->device->host->hostdata;
if (tw_dev == NULL) {
printk(KERN_WARNING "3w-xxxx: tw_scsi_eh_abort(): Invalid device extension.\n");
return (FAILED);
}
spin_lock(&tw_dev->tw_lock);
tw_dev->num_aborts++;
/* If the command hasn't been posted yet, we can do the abort */
for (i=0;i<TW_Q_LENGTH;i++) {
if (tw_dev->srb[i] == SCpnt) {
if (tw_dev->state[i] == TW_S_STARTED) {
printk(KERN_WARNING "3w-xxxx: scsi%d: Unit #%d: Command (%p) timed out.\n", tw_dev->host->host_no, tw_dev->srb[i]==0 ? 0 : tw_dev->srb[i]->device->id, SCpnt);
tw_dev->state[i] = TW_S_COMPLETED;
tw_state_request_finish(tw_dev, i);
spin_unlock(&tw_dev->tw_lock);
return (SUCCESS);
}
if (tw_dev->state[i] == TW_S_PENDING) {
printk(KERN_WARNING "3w-xxxx: scsi%d: Unit #%d: Command (%p) timed out.\n", tw_dev->host->host_no, tw_dev->srb[i]==0 ? 0 : tw_dev->srb[i]->device->id, SCpnt);
if (tw_dev->pending_head == TW_Q_LENGTH-1) {
tw_dev->pending_head = TW_Q_START;
} else {
tw_dev->pending_head = tw_dev->pending_head + 1;
}
tw_dev->pending_request_count--;
tw_dev->state[i] = TW_S_COMPLETED;
tw_state_request_finish(tw_dev, i);
spin_unlock(&tw_dev->tw_lock);
return (SUCCESS);
}
if (tw_dev->state[i] == TW_S_POSTED) {
/* If the command has already been posted, we have to reset the card */
printk(KERN_WARNING "3w-xxxx: scsi%d: Unit #%d: Command (%p) timed out, resetting card.\n", tw_dev->host->host_no, tw_dev->srb[i]==0 ? 0 : tw_dev->srb[i]->device->id, SCpnt);
/* We have to let AEN requests through before the reset */
spin_unlock(&tw_dev->tw_lock);
spin_unlock_irq(tw_dev->host->host_lock);
mdelay(TW_AEN_WAIT_TIME);
spin_lock_irq(tw_dev->host->host_lock);
spin_lock(&tw_dev->tw_lock);
if (tw_reset_device_extension(tw_dev)) {
dprintk(KERN_WARNING "3w-xxxx: tw_scsi_eh_abort(): Reset failed for card %d.\n", tw_dev->host->host_no);
spin_unlock(&tw_dev->tw_lock);
return (FAILED);
}
}
}
}
spin_unlock(&tw_dev->tw_lock);
return (SUCCESS);
} /* End tw_scsi_eh_abort() */
/* This is the new scsi eh reset function */
int tw_scsi_eh_reset(Scsi_Cmnd *SCpnt)
{
TW_Device_Extension *tw_dev=NULL;
dprintk(KERN_NOTICE "3w-xxxx: tw_scsi_eh_reset()\n");
if (!SCpnt) {
printk(KERN_WARNING "3w-xxxx: tw_scsi_eh_reset(): Invalid Scsi_Cmnd.\n");
return (FAILED);
}
tw_dev = (TW_Device_Extension *)SCpnt->device->host->hostdata;
if (tw_dev == NULL) {
printk(KERN_WARNING "3w-xxxx: tw_scsi_eh_reset(): Invalid device extension.\n");
return (FAILED);
}
/* We have to let AEN requests through before the reset */
spin_unlock_irq(tw_dev->host->host_lock);
mdelay(TW_AEN_WAIT_TIME);
spin_lock_irq(tw_dev->host->host_lock);
spin_lock(&tw_dev->tw_lock);
tw_dev->num_resets++;
/* Now reset the card and some of the device extension data */
if (tw_reset_device_extension(tw_dev)) {
printk(KERN_WARNING "3w-xxxx: scsi%d: Reset failed.\n", tw_dev->host->host_no);
spin_unlock(&tw_dev->tw_lock);
return (FAILED);
}
printk(KERN_WARNING "3w-xxxx: scsi%d: Reset succeeded.\n", tw_dev->host->host_no);
spin_unlock(&tw_dev->tw_lock);
return (SUCCESS);
} /* End tw_scsi_eh_reset() */
/* This function handles input and output from /proc/scsi/3w-xxxx/x */
int tw_scsi_proc_info(char *buffer, char **start, off_t offset, int length, int hostno, int inout)
{
TW_Device_Extension *tw_dev = NULL;
TW_Info info;
int i;
int j;
dprintk(KERN_NOTICE "3w-xxxx: tw_scsi_proc_info()\n");
/* Find the correct device extension */
for (i=0;i<tw_device_extension_count;i++)
if (tw_device_extension_list[i]->host->host_no == hostno)
tw_dev = tw_device_extension_list[i];
if (tw_dev == NULL) {
printk(KERN_WARNING "3w-xxxx: tw_scsi_proc_info(): Couldn't locate device extension.\n");
return (-EINVAL);
}
info.buffer = buffer;
info.length = length;
info.offset = offset;
info.position = 0;
if (inout) {
/* Write */
if (strncmp(buffer, "debug", 5) == 0) {
printk(KERN_INFO "3w-xxxx: Posted commands:\n");
for (j=0;j<TW_Q_LENGTH;j++) {
if (tw_dev->state[j] == TW_S_POSTED) {
TW_Command *command = (TW_Command *)tw_dev->command_packet_virtual_address[j];
printk(KERN_INFO "3w-xxxx: Request_id: %d\n", j);
printk(KERN_INFO "Opcode: 0x%x\n", command->byte0.opcode);
printk(KERN_INFO "Block_count: 0x%x\n", command->byte6.block_count);
printk(KERN_INFO "LBA: 0x%x\n", command->byte8.io.lba);
printk(KERN_INFO "Physical command packet addr: 0x%lx\n", tw_dev->command_packet_physical_address[j]);
printk(KERN_INFO "Scsi_Cmnd: %p\n", tw_dev->srb[j]);
}
}
printk(KERN_INFO "3w-xxxx: Free_head: %3d\n", tw_dev->free_head);
printk(KERN_INFO "3w-xxxx: Free_tail: %3d\n", tw_dev->free_tail);
}
return length;
} else {
/* Read */
if (start) {
*start = buffer;
}
tw_copy_info(&info, "scsi%d: 3ware Storage Controller\n", hostno);
tw_copy_info(&info, "Driver version: %s\n", tw_driver_version);
tw_copy_info(&info, "Current commands posted: %3d\n", tw_dev->posted_request_count);
tw_copy_info(&info, "Max commands posted: %3d\n", tw_dev->max_posted_request_count);
tw_copy_info(&info, "Current pending commands: %3d\n", tw_dev->pending_request_count);
tw_copy_info(&info, "Max pending commands: %3d\n", tw_dev->max_pending_request_count);
tw_copy_info(&info, "Last sgl length: %3d\n", tw_dev->sgl_entries);
tw_copy_info(&info, "Max sgl length: %3d\n", tw_dev->max_sgl_entries);
tw_copy_info(&info, "Last sector count: %3d\n", tw_dev->sector_count);
tw_copy_info(&info, "Max sector count: %3d\n", tw_dev->max_sector_count);
tw_copy_info(&info, "Resets: %3d\n", tw_dev->num_resets);
tw_copy_info(&info, "Aborts: %3d\n", tw_dev->num_aborts);
tw_copy_info(&info, "AEN's: %3d\n", tw_dev->aen_count);
}
if (info.position > info.offset) {
return (info.position - info.offset);
} else {
return 0;
}
} /* End tw_scsi_proc_info() */
/* This is the main scsi queue function to handle scsi opcodes */
int tw_scsi_queue(Scsi_Cmnd *SCpnt, void (*done)(Scsi_Cmnd *))
{
unsigned char *command = SCpnt->cmnd;
int request_id = 0;
int error = 0;
TW_Device_Extension *tw_dev = (TW_Device_Extension *)SCpnt->device->host->hostdata;
if (tw_dev == NULL) {