summaryrefslogtreecommitdiff
path: root/drivers/scsi/hpsa.c
diff options
context:
space:
mode:
authorStephen M. Cameron <scameron@beardog.cce.hp.com>2014-02-18 13:55:43 -0600
committerJames Bottomley <JBottomley@Parallels.com>2014-03-15 10:19:04 -0700
commit76438d087f18fc0ce0431bf22e5195ec3db1656c (patch)
tree8144e1687995df13849d68648fb166eab9f8f83c /drivers/scsi/hpsa.c
parent250fb125ff94465a20429c4765f079aa142c9d80 (diff)
[SCSI] hpsa: poll controller to detect device change event
For shared SAS configurations, hosts need to poll Smart Arrays periodically in order to be able to detect configuration changes such as logical drives being added or removed from remote hosts. A register on the controller indicates when such events have occurred, and the driver polls the register via a workqueue and kicks off a rescan of devices if such an event is detected. Additionally, changes to logical drive raid offload eligibility are autodetected in this way. Signed-off-by: Stephen M. Cameron <scameron@beardog.cce.hp.com> Signed-off-by: Scott Teel <scott.teel@hp.com> Signed-off-by: James Bottomley <JBottomley@Parallels.com>
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.