summaryrefslogtreecommitdiff
path: root/drivers/scsi/hpsa.c
diff options
context:
space:
mode:
Diffstat (limited to 'drivers/scsi/hpsa.c')
-rw-r--r--drivers/scsi/hpsa.c108
1 files changed, 108 insertions, 0 deletions
diff --git a/drivers/scsi/hpsa.c b/drivers/scsi/hpsa.c
index 98af5c3ed679..ee17556058c2 100644
--- a/drivers/scsi/hpsa.c
+++ b/drivers/scsi/hpsa.c
@@ -220,6 +220,8 @@ static inline void finish_cmd(struct CommandList *c);
static void hpsa_wait_for_mode_change_ack(struct ctlr_info *h);
#define BOARD_NOT_READY 0
#define BOARD_READY 1
+static void hpsa_drain_commands(struct ctlr_info *h);
+static void hpsa_flush_cache(struct ctlr_info *h);
static inline struct ctlr_info *sdev_to_hba(struct scsi_device *sdev)
{
@@ -5025,6 +5027,23 @@ static inline void hpsa_p600_dma_prefetch_quirk(struct ctlr_info *h)
writel(dma_prefetch, h->vaddr + I2O_DMA1_CFG);
}
+static void hpsa_wait_for_clear_event_notify_ack(struct ctlr_info *h)
+{
+ int i;
+ u32 doorbell_value;
+ unsigned long flags;
+ /* wait until the clear_event_notify bit 6 is cleared by controller. */
+ for (i = 0; i < MAX_CONFIG_WAIT; i++) {
+ spin_lock_irqsave(&h->lock, flags);
+ doorbell_value = readl(h->vaddr + SA5_DOORBELL);
+ spin_unlock_irqrestore(&h->lock, flags);
+ if (!(doorbell_value & DOORBELL_CLEAR_EVENTS))
+ break;
+ /* delay and try again */
+ msleep(20);
+ }
+}
+
static void hpsa_wait_for_mode_change_ack(struct ctlr_info *h)
{
int i;
@@ -5401,6 +5420,79 @@ static void detect_controller_lockup(struct ctlr_info *h)
h->last_heartbeat_timestamp = now;
}
+static int hpsa_kickoff_rescan(struct ctlr_info *h)
+{
+ int i;
+ char *event_type;
+
+ /* Ask the controller to clear the events we're handling. */
+ if (h->transMethod & (CFGTBL_Trans_io_accel1) &&
+ (h->events & HPSA_EVENT_NOTIFY_ACCEL_IO_PATH_STATE_CHANGE ||
+ h->events & HPSA_EVENT_NOTIFY_ACCEL_IO_PATH_CONFIG_CHANGE)) {
+
+ if (h->events & HPSA_EVENT_NOTIFY_ACCEL_IO_PATH_STATE_CHANGE)
+ event_type = "state change";
+ if (h->events & HPSA_EVENT_NOTIFY_ACCEL_IO_PATH_CONFIG_CHANGE)
+ event_type = "configuration change";
+ /* Stop sending new RAID offload reqs via the IO accelerator */
+ scsi_block_requests(h->scsi_host);
+ for (i = 0; i < h->ndevices; i++)
+ h->dev[i]->offload_enabled = 0;
+ hpsa_drain_commands(h);
+ /* Set 'accelerator path config change' bit */
+ dev_warn(&h->pdev->dev,
+ "Acknowledging event: 0x%08x (HP SSD Smart Path %s)\n",
+ h->events, event_type);
+ writel(h->events, &(h->cfgtable->clear_event_notify));
+ /* Set the "clear event notify field update" bit 6 */
+ writel(DOORBELL_CLEAR_EVENTS, h->vaddr + SA5_DOORBELL);
+ /* Wait until ctlr clears 'clear event notify field', bit 6 */
+ hpsa_wait_for_clear_event_notify_ack(h);
+ scsi_unblock_requests(h->scsi_host);
+ } else {
+ /* Acknowledge controller notification events. */
+ writel(h->events, &(h->cfgtable->clear_event_notify));
+ writel(DOORBELL_CLEAR_EVENTS, h->vaddr + SA5_DOORBELL);
+ hpsa_wait_for_clear_event_notify_ack(h);
+#if 0
+ writel(CFGTBL_ChangeReq, h->vaddr + SA5_DOORBELL);
+ hpsa_wait_for_mode_change_ack(h);
+#endif
+ }
+
+ /* Something in the device list may have changed to trigger
+ * the event, so do a rescan.
+ */
+ hpsa_scan_start(h->scsi_host);
+ /* release reference taken on scsi host in check_controller_events */
+ scsi_host_put(h->scsi_host);
+ return 0;
+}
+
+/* Check a register on the controller to see if there are configuration
+ * changes (added/changed/removed logical drives, etc.) which mean that
+ * we should rescan the controller for devices. If so, add the controller
+ * to the list of controllers needing to be rescanned, and gets a
+ * reference to the associated scsi_host.
+ */
+static void hpsa_ctlr_needs_rescan(struct ctlr_info *h)
+{
+ if (!(h->fw_support & MISC_FW_EVENT_NOTIFY))
+ return;
+
+ h->events = readl(&(h->cfgtable->event_notify));
+ if (!h->events)
+ return;
+
+ /*
+ * Take a reference on scsi host for the duration of the scan
+ * Release in hpsa_kickoff_rescan(). No lock needed for scan_list
+ * as only a single thread accesses this list.
+ */
+ scsi_host_get(h->scsi_host);
+ hpsa_kickoff_rescan(h);
+}
+
static void hpsa_monitor_ctlr_worker(struct work_struct *work)
{
unsigned long flags;
@@ -5409,6 +5501,7 @@ static void hpsa_monitor_ctlr_worker(struct work_struct *work)
detect_controller_lockup(h);
if (h->lockup_detected)
return;
+ hpsa_ctlr_needs_rescan(h);
spin_lock_irqsave(&h->lock, flags);
if (h->remove_in_progress) {
spin_unlock_irqrestore(&h->lock, flags);
@@ -5950,6 +6043,21 @@ clean_up:
kfree(h->blockFetchTable);
}
+static void hpsa_drain_commands(struct ctlr_info *h)
+{
+ int cmds_out;
+ unsigned long flags;
+
+ do { /* wait for all outstanding commands to drain out */
+ spin_lock_irqsave(&h->lock, flags);
+ cmds_out = h->commands_outstanding;
+ spin_unlock_irqrestore(&h->lock, flags);
+ if (cmds_out <= 0)
+ break;
+ msleep(100);
+ } while (1);
+}
+
/*
* This is it. Register the PCI driver information for the cards we control
* the OS will call our registered routines when it finds one of our cards.