[SCSI] hpsa: poll controller to detect device change event
authorStephen M. Cameron <scameron@beardog.cce.hp.com>
Tue, 18 Feb 2014 19:55:43 +0000 (13:55 -0600)
committerJames Bottomley <JBottomley@Parallels.com>
Sat, 15 Mar 2014 17:19:04 +0000 (10:19 -0700)
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>
drivers/scsi/hpsa.c
drivers/scsi/hpsa.h
drivers/scsi/hpsa_cmd.h

index 98af5c3ed6791b2ac813ddc496cb73ef14b4a8f9..ee17556058c2cfc7a4b934becd221ee8081328f7 100644 (file)
@@ -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.
index ae08f1c46272567e8d432c9deba245abbb1cc28b..df2f88df10be0859fe9e73cb056e7811ffdbd295 100644 (file)
@@ -176,6 +176,7 @@ struct ctlr_info {
 #define HPSATMF_LOG_QRY_TASK    (1 << 23)
 #define HPSATMF_LOG_QRY_TSET    (1 << 24)
 #define HPSATMF_LOG_QRY_ASYNC   (1 << 25)
+       u32 events;
 };
 #define HPSA_ABORT_MSG 0
 #define HPSA_DEVICE_RESET_MSG 1
index c1ae8d2a6bf2ab7889a25650f474f4a38b124a9d..21f8a616e997255551a27e188b893918ca8c8731 100644 (file)
 #define CFGTBL_AccCmds          0x00000001l
 #define DOORBELL_CTLR_RESET    0x00000004l
 #define DOORBELL_CTLR_RESET2   0x00000020l
+#define DOORBELL_CLEAR_EVENTS  0x00000040l
 
 #define CFGTBL_Trans_Simple     0x00000002l
 #define CFGTBL_Trans_Performant 0x00000004l
@@ -495,6 +496,8 @@ struct CfgTable {
        u32             io_accel_max_embedded_sg_count;
        u32             io_accel_request_size_offset;
        u32             event_notify;
+#define HPSA_EVENT_NOTIFY_ACCEL_IO_PATH_STATE_CHANGE (1 << 30)
+#define HPSA_EVENT_NOTIFY_ACCEL_IO_PATH_CONFIG_CHANGE (1 << 31)
        u32             clear_event_notify;
 };