summaryrefslogtreecommitdiffstats
path: root/drivers/scsi/3w-xxxx.c
diff options
context:
space:
mode:
authorRalf Baechle <ralf@linux-mips.org>2000-07-23 14:05:01 +0000
committerRalf Baechle <ralf@linux-mips.org>2000-07-23 14:05:01 +0000
commitf3627cbe9236a062012c836f3b6ee311b43f63f2 (patch)
treeae854838b9a73b35bd0f3b8f42e5fb7f9cb1d5a9 /drivers/scsi/3w-xxxx.c
parentfea12a7b3f20bc135ab533491411e9ff753c01c8 (diff)
Merge with Linux 2.4.0-test5-pre4.
Diffstat (limited to 'drivers/scsi/3w-xxxx.c')
-rw-r--r--drivers/scsi/3w-xxxx.c193
1 files changed, 179 insertions, 14 deletions
diff --git a/drivers/scsi/3w-xxxx.c b/drivers/scsi/3w-xxxx.c
index b3b8d17fb..ebe4e33ba 100644
--- a/drivers/scsi/3w-xxxx.c
+++ b/drivers/scsi/3w-xxxx.c
@@ -2,6 +2,8 @@
3w-xxxx.c -- 3ware Storage Controller device driver for Linux.
Written By: Adam Radford <linux@3ware.com>
+ Modifications By: Joel Jacobson <linux@3ware.com>
+
Copyright (C) 1999-2000 3ware Inc.
Kernel compatablity By: Andre Hedrick <andre@suse.com>
@@ -47,6 +49,19 @@
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.
*/
#include <linux/module.h>
@@ -65,6 +80,7 @@ MODULE_DESCRIPTION ("3ware Storage Controller Linux Driver");
#include <linux/string.h>
#include <linux/delay.h>
#include <linux/smp.h>
+#include <linux/reboot.h>
#include <linux/spinlock.h>
#include <asm/errno.h>
@@ -83,9 +99,15 @@ MODULE_DESCRIPTION ("3ware Storage Controller Linux Driver");
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);
+
+/* Notifier block to get a notify on system shutdown/halt/reboot */
+static struct notifier_block tw_notifier = {
+ tw_halt, NULL, 0
+};
/* Globals */
-char *tw_driver_version="1.1.000";
+char *tw_driver_version="1.02.00.001";
TW_Device_Extension *tw_device_extension_list[TW_MAX_SLOT];
int tw_device_extension_count = 0;
@@ -224,7 +246,7 @@ int tw_aen_drain_queue(TW_Device_Extension *tw_dev)
if (command_packet->status != 0) {
if (command_packet->flags != TW_AEN_TABLE_UNDEFINED) {
/* Bad response */
- printk(KERN_WARNING "3w-xxxx: tw_aen_drain_queue(): Bad response, flags = 0x%x.\n", command_packet->flags);
+ printk(KERN_WARNING "3w-xxxx: tw_aen_drain_queue(): Bad response, status = 0x%x, flags = 0x%x.\n", command_packet->status, command_packet->flags);
return 1;
} else {
/* We know this is a 3w-1x00, and doesn't support aen's */
@@ -562,6 +584,7 @@ int tw_findcards(Scsi_Host_Template *tw_host)
TW_Device_Extension *tw_dev2;
struct pci_dev *tw_pci_dev = NULL;
u32 status_reg_value;
+ unsigned char c = 1;
dprintk(KERN_NOTICE "3w-xxxx: tw_findcards()\n");
while ((tw_pci_dev = pci_find_device(TW_VENDOR_ID, TW_DEVICE_ID, tw_pci_dev))) {
@@ -662,7 +685,7 @@ int tw_findcards(Scsi_Host_Template *tw_host)
continue;
}
- error = tw_initconnection(tw_dev);
+ error = tw_initconnection(tw_dev, TW_INIT_MESSAGE_CREDITS);
if (error) {
printk(KERN_WARNING "3w-xxxx: tw_findcards(): Couldn't initconnection for card %d.\n", numcards);
release_region((tw_dev->tw_pci_dev->resource[0].start), TW_IO_ADDRESS_RANGE);
@@ -722,10 +745,15 @@ int tw_findcards(Scsi_Host_Template *tw_host)
/* Free the temporary device extension */
if (tw_dev)
kfree(tw_dev);
+
+ /* Tell the firmware we support shutdown notification*/
+ tw_setfeature(tw_dev, 2, 1, &c);
}
if (numcards == 0)
printk(KERN_WARNING "3w-xxxx: tw_findcards(): No cards found.\n");
+ else
+ register_reboot_notifier(&tw_notifier);
return numcards;
} /* End tw_findcards() */
@@ -747,8 +775,22 @@ void tw_free_device_extension(TW_Device_Extension *tw_dev)
}
} /* 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++) {
+ printk(KERN_NOTICE "3w-xxxx: Notifying card #%d\n", i);
+ tw_shutdown_device(tw_device_extension_list[i]);
+ }
+ 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 tw_initconnection(TW_Device_Extension *tw_dev, int message_credits)
{
u32 command_que_addr, command_que_value;
u32 status_reg_addr, status_reg_value;
@@ -780,7 +822,7 @@ int tw_initconnection(TW_Device_Extension *tw_dev)
command_packet->byte3.host_id = 0x0;
command_packet->status = 0x0;
command_packet->flags = 0x0;
- command_packet->byte6.message_credits = TW_INIT_MESSAGE_CREDITS;
+ 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];
@@ -811,7 +853,7 @@ int tw_initconnection(TW_Device_Extension *tw_dev)
}
if (command_packet->status != 0) {
/* bad response */
- printk(KERN_WARNING "3w-xxxx: tw_initconnection(): Bad response, flags = 0x%x.\n", command_packet->flags);
+ printk(KERN_WARNING "3w-xxxx: tw_initconnection(): Bad response, status = 0x%x, flags = 0x%x.\n", command_packet->status, command_packet->flags);
return 1;
}
break; /* Response was okay, so we exit */
@@ -959,7 +1001,7 @@ int tw_initialize_units(TW_Device_Extension *tw_dev)
}
if (command_packet->status != 0) {
/* bad response */
- printk(KERN_WARNING "3w-xxxx: tw_initialize_units(): Bad response, flags = 0x%x.\n", command_packet->flags);
+ printk(KERN_WARNING "3w-xxxx: tw_initialize_units(): Bad response, status = 0x%x, flags = 0x%x.\n", command_packet->status, command_packet->flags);
return 1;
}
found = 1;
@@ -981,9 +1023,11 @@ int tw_initialize_units(TW_Device_Extension *tw_dev)
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;
@@ -1089,7 +1133,7 @@ static void tw_interrupt(int irq, void *dev_instance, struct pt_regs *regs)
request_id = response_que.u.response_id;
command_packet = (TW_Command *)tw_dev->command_packet_virtual_address[request_id];
if (command_packet->status != 0) {
- printk(KERN_WARNING "3w-xxxx: tw_interrupt(): Bad response, flags = 0x%x.\n", command_packet->flags);
+ printk(KERN_WARNING "3w-xxxx: tw_interrupt(): Bad response, status = 0x%x, flags = 0x%x.\n", command_packet->status, command_packet->flags);
}
if (tw_dev->state[request_id] != TW_S_POSTED) {
printk(KERN_WARNING "3w-xxxx: tw_interrupt(): Received a request id (%d) (opcode = 0x%x) that wasn't posted.\n", request_id, command_packet->byte0.opcode);
@@ -1186,7 +1230,7 @@ int tw_ioctl(TW_Device_Extension *tw_dev, int request_id)
/* Initialize command packet */
command_packet = (TW_Command *)tw_dev->command_packet_virtual_address[request_id];
if (command_packet == NULL) {
- printk(KERN_WARNING "3w-xxxx: twioctl(): Bad command packet virtual address.\n");
+ 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);
@@ -1256,6 +1300,12 @@ int tw_ioctl(TW_Device_Extension *tw_dev, int 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_CMD_PACKET:
+ memcpy(command_packet, ioctl->data, sizeof(TW_Command));
+ command_packet->request_id = request_id;
+ tw_post_command_packet(tw_dev, request_id);
+
+ return 0;
default:
printk(KERN_WARNING "3w-xxxx: Unknown ioctl 0x%x.\n", opcode);
tw_dev->state[request_id] = TW_S_COMPLETED;
@@ -1480,7 +1530,7 @@ int tw_reset_sequence(TW_Device_Extension *tw_dev)
return 1;
}
- error = tw_initconnection(tw_dev);
+ error = tw_initconnection(tw_dev, TW_INIT_MESSAGE_CREDITS);
if (error) {
printk(KERN_WARNING "3w-xxxx: tw_reset_sequence(): Couldn't initconnection for card %d.\n", tw_dev->host->host_no);
return 1;
@@ -1598,7 +1648,6 @@ int tw_scsi_eh_abort(Scsi_Cmnd *SCpnt)
int tw_scsi_eh_reset(Scsi_Cmnd *SCpnt)
{
TW_Device_Extension *tw_dev=NULL;
- int flags = 0;
dprintk(KERN_NOTICE "3w-xxxx: tw_scsi_eh_reset()\n");
@@ -1613,17 +1662,17 @@ int tw_scsi_eh_reset(Scsi_Cmnd *SCpnt)
return (FAILED);
}
- spin_lock_irqsave(&tw_dev->tw_lock, flags);
+ 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: tw_scsi_eh_reset(): Reset failed for card %d.\n", tw_dev->host->host_no);
- spin_unlock_irqrestore(&tw_dev->tw_lock, flags);
+ spin_unlock(&tw_dev->tw_lock);
return (FAILED);
}
printk(KERN_WARNING "3w-xxxx: tw_scsi_eh_reset(): Reset succeeded for card %d.\n", tw_dev->host->host_no);
- spin_unlock_irqrestore(&tw_dev->tw_lock, flags);
+ spin_unlock(&tw_dev->tw_lock);
return (SUCCESS);
} /* End tw_scsi_eh_reset() */
@@ -1803,6 +1852,11 @@ int tw_scsi_release(struct Scsi_Host *tw_host)
/* Tell kernel scsi-layer we are gone */
scsi_unregister(tw_host);
+
+ /* Fake like we just shut down, so notify the card that
+ * we "shut down cleanly".
+ */
+ tw_halt(0, 0, 0); // parameters aren't actually used
return 0;
} /* End tw_scsi_release() */
@@ -1901,8 +1955,10 @@ int tw_scsiop_inquiry_complete(TW_Device_Extension *tw_dev, int request_id)
if (is_unit_present[i] == 0) {
tw_dev->is_unit_present[i] = FALSE;
} else {
+ if (is_unit_present[i] & TW_UNIT_ONLINE) {
tw_dev->is_unit_present[i] = TRUE;
dprintk(KERN_NOTICE "3w-xxxx: tw_scsiop_inquiry_complete: Unit %d found.\n", i);
+ }
}
}
@@ -2124,6 +2180,92 @@ int tw_scsiop_test_unit_ready(TW_Device_Extension *tw_dev, int request_id)
return 0;
} /* End tw_scsiop_test_unit_ready() */
+/* Set a value in the features table */
+int tw_setfeature(TW_Device_Extension *tw_dev, int parm, int param_size,
+ unsigned char *val)
+{
+ TW_Param *param;
+ TW_Command *command_packet;
+ TW_Response_Queue response_queue;
+ int request_id = 0;
+ u32 command_que_value, command_que_addr;
+ u32 status_reg_addr, status_reg_value;
+ u32 response_que_addr;
+ u32 param_value;
+ int imax, i;
+
+ /* Initialize SetParam command packet */
+ if (tw_dev->command_packet_virtual_address[request_id] == NULL) {
+ printk(KERN_WARNING "3w-xxxx: tw_setfeature(): 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));
+ param = (TW_Param *)tw_dev->alignment_virtual_address[request_id];
+
+ command_packet->byte0.opcode = TW_OP_SET_PARAM;
+ command_packet->byte0.sgl_offset = 2;
+ param->table_id = 0x404; /* Features table */
+ param->parameter_id = parm;
+ param->parameter_size_bytes = param_size;
+ memcpy(param->data, val, param_size);
+
+ 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->size = 4;
+ command_packet->request_id = request_id;
+ 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_setfeature(): Bad command packet physical address.\n");
+ return 1;
+ }
+ command_que_addr = tw_dev->registers.command_que_addr;
+ status_reg_addr = tw_dev->registers.status_reg_addr;
+ response_que_addr = tw_dev->registers.response_que_addr;
+
+ /* Send command packet to the board */
+ outl(command_que_value, command_que_addr);
+
+ /* Poll for completion */
+ imax = TW_POLL_MAX_RETRIES;
+ for (i=0;i<imax;i++) {
+ mdelay(10);
+ status_reg_value = inl(status_reg_addr);
+ if (tw_check_bits(status_reg_value)) {
+ printk(KERN_WARNING "3w-xxxx: tw_setfeature(): Unexpected bits.\n");
+ return 1;
+ }
+ if ((status_reg_value & TW_STATUS_RESPONSE_QUEUE_EMPTY) == 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_setfeature(): Unexpected request id.\n");
+ return 1;
+ }
+ if (command_packet->status != 0) {
+ /* bad response */
+ printk(KERN_WARNING "3w-xxxx: tw_setfeature(): Bad response, status = 0x%x, flags = 0x%x.\n", command_packet->status, command_packet->flags);
+ return 1;
+ }
+ break; /* Response was okay, so we exit */
+ }
+ }
+
+ return 0;
+} /* End tw_setfeature() */
+
/* This function will setup the interrupt handler */
int tw_setup_irq(TW_Device_Extension *tw_dev)
{
@@ -2140,6 +2282,29 @@ int tw_setup_irq(TW_Device_Extension *tw_dev)
return 0;
} /* End tw_setup_irq() */
+/* This function will tell the controller we're shutting down by sending
+ initconnection with a 1 */
+int tw_shutdown_device(TW_Device_Extension *tw_dev)
+{
+ int error;
+
+ /* Disable interrupts */
+ tw_disable_interrupts(tw_dev);
+
+ /* poke the board */
+ error = tw_initconnection(tw_dev, 1);
+ if (error) {
+ printk(KERN_WARNING "3w-xxxx: tw_shutdown_device(): Couldn't initconnection for card %d.\n", tw_dev->host->host_no);
+ } else {
+ printk(KERN_NOTICE "3w-xxxx shutdown succeeded\n");
+ }
+
+ /* Re-enable interrupts */
+ tw_enable_interrupts(tw_dev);
+
+ return 0;
+} /* End tw_shutdown_device() */
+
/* This function will soft reset the controller */
void tw_soft_reset(TW_Device_Extension *tw_dev)
{