]> git.karo-electronics.de Git - karo-tx-linux.git/blobdiff - drivers/block/cciss.c
cciss: forbid hard reset of 640x boards
[karo-tx-linux.git] / drivers / block / cciss.c
index b70247110608679b1ae5189895fa479dce55742a..2abe6df9445e2845f8c0eabd228385c209a138b2 100644 (file)
@@ -216,6 +216,12 @@ static void cciss_device_release(struct device *dev);
 static void cciss_free_gendisk(ctlr_info_t *h, int drv_index);
 static void cciss_free_drive_info(ctlr_info_t *h, int drv_index);
 static inline u32 next_command(ctlr_info_t *h);
+static int __devinit cciss_find_cfg_addrs(struct pci_dev *pdev,
+       void __iomem *vaddr, u32 *cfg_base_addr, u64 *cfg_base_addr_index,
+       u64 *cfg_offset);
+static int __devinit cciss_pci_find_memory_BAR(struct pci_dev *pdev,
+       unsigned long *memory_bar);
+
 
 /* performant mode helper functions */
 static void  calc_bucket_map(int *bucket, int num_buckets, int nsgs,
@@ -3711,9 +3717,9 @@ static int check_for_unit_attention(ctlr_info_t *h, CommandList_struct *c)
  *   the io functions.
  *   This is for debug only.
  */
-#ifdef CCISS_DEBUG
 static void print_cfg_table(CfgTable_struct *tb)
 {
+#ifdef CCISS_DEBUG
        int i;
        char temp_name[17];
 
@@ -3742,8 +3748,8 @@ static void print_cfg_table(CfgTable_struct *tb)
        temp_name[16] = '\0';
        printk("   Server Name = %s\n", temp_name);
        printk("   Heartbeat Counter = 0x%x\n\n\n", readl(&(tb->HeartBeat)));
-}
 #endif                         /* CCISS_DEBUG */
+}
 
 static int find_PCI_BAR_index(struct pci_dev *pdev, unsigned long pci_bar_addr)
 {
@@ -3816,55 +3822,50 @@ static void  calc_bucket_map(int bucket[], int num_buckets,
        }
 }
 
-static void
-cciss_put_controller_into_performant_mode(ctlr_info_t *h)
+static void __devinit cciss_wait_for_mode_change_ack(ctlr_info_t *h)
 {
-       int l = 0;
-       __u32 trans_support;
+       int i;
+
+       /* under certain very rare conditions, this can take awhile.
+        * (e.g.: hot replace a failed 144GB drive in a RAID 5 set right
+        * as we enter this code.) */
+       for (i = 0; i < MAX_CONFIG_WAIT; i++) {
+               if (!(readl(h->vaddr + SA5_DOORBELL) & CFGTBL_ChangeReq))
+                       break;
+               msleep(10);
+       }
+}
+
+static __devinit void cciss_enter_performant_mode(ctlr_info_t *h)
+{
+       /* This is a bit complicated.  There are 8 registers on
+        * the controller which we write to to tell it 8 different
+        * sizes of commands which there may be.  It's a way of
+        * reducing the DMA done to fetch each command.  Encoded into
+        * each command's tag are 3 bits which communicate to the controller
+        * which of the eight sizes that command fits within.  The size of
+        * each command depends on how many scatter gather entries there are.
+        * Each SG entry requires 16 bytes.  The eight registers are programmed
+        * with the number of 16-byte blocks a command of that size requires.
+        * The smallest command possible requires 5 such 16 byte blocks.
+        * the largest command possible requires MAXSGENTRIES + 4 16-byte
+        * blocks.  Note, this only extends to the SG entries contained
+        * within the command block, and does not extend to chained blocks
+        * of SG elements.   bft[] contains the eight values we write to
+        * the registers.  They are not evenly distributed, but have more
+        * sizes for small commands, and fewer sizes for larger commands.
+        */
        __u32 trans_offset;
+       int bft[8] = { 5, 6, 8, 10, 12, 20, 28, MAXSGENTRIES + 4};
                        /*
                         *  5 = 1 s/g entry or 4k
                         *  6 = 2 s/g entry or 8k
                         *  8 = 4 s/g entry or 16k
                         * 10 = 6 s/g entry or 24k
                         */
-       int bft[8] = { 5, 6, 8, 10, 12, 20, 28, MAXSGENTRIES + 4};
        unsigned long register_value;
-
        BUILD_BUG_ON(28 > MAXSGENTRIES + 4);
 
-       /* Attempt to put controller into performant mode if supported */
-       /* Does board support performant mode? */
-       trans_support = readl(&(h->cfgtable->TransportSupport));
-       if (!(trans_support & PERFORMANT_MODE))
-               return;
-
-       printk(KERN_WARNING "cciss%d: Placing controller into "
-                               "performant mode\n", h->ctlr);
-       /* Performant mode demands commands on a 32 byte boundary
-        * pci_alloc_consistent aligns on page boundarys already.
-        * Just need to check if divisible by 32
-        */
-       if ((sizeof(CommandList_struct) % 32) != 0) {
-               printk(KERN_WARNING "%s %d %s\n",
-                       "cciss info: command size[",
-                       (int)sizeof(CommandList_struct),
-                       "] not divisible by 32, no performant mode..\n");
-               return;
-       }
-
-       /* Performant mode ring buffer and supporting data structures */
-       h->reply_pool = (__u64 *)pci_alloc_consistent(
-               h->pdev, h->max_commands * sizeof(__u64),
-               &(h->reply_pool_dhandle));
-
-       /* Need a block fetch table for performant mode */
-       h->blockFetchTable = kmalloc(((h->maxsgentries+1) *
-               sizeof(__u32)), GFP_KERNEL);
-
-       if ((h->reply_pool == NULL) || (h->blockFetchTable == NULL))
-               goto clean_up;
-
        h->reply_pool_wraparound = 1; /* spec: init to 1 */
 
        /* Controller spec: zero out this buffer. */
@@ -3893,28 +3894,56 @@ cciss_put_controller_into_performant_mode(ctlr_info_t *h)
        writel(CFGTBL_Trans_Performant,
                        &(h->cfgtable->HostWrite.TransportRequest));
 
-       h->transMethod = CFGTBL_Trans_Performant;
        writel(CFGTBL_ChangeReq, h->vaddr + SA5_DOORBELL);
-       /* under certain very rare conditions, this can take awhile.
-        * (e.g.: hot replace a failed 144GB drive in a RAID 5 set right
-        * as we enter this code.) */
-       for (l = 0; l < MAX_CONFIG_WAIT; l++) {
-               register_value = readl(h->vaddr + SA5_DOORBELL);
-               if (!(register_value & CFGTBL_ChangeReq))
-                       break;
-               /* delay and try again */
-               set_current_state(TASK_INTERRUPTIBLE);
-               schedule_timeout(10);
-       }
+       cciss_wait_for_mode_change_ack(h);
        register_value = readl(&(h->cfgtable->TransportActive));
-       if (!(register_value & CFGTBL_Trans_Performant)) {
+       if (!(register_value & CFGTBL_Trans_Performant))
                printk(KERN_WARNING "cciss: unable to get board into"
                                        " performant mode\n");
+}
+
+static void __devinit cciss_put_controller_into_performant_mode(ctlr_info_t *h)
+{
+       __u32 trans_support;
+
+       dev_dbg(&h->pdev->dev, "Trying to put board into Performant mode\n");
+       /* Attempt to put controller into performant mode if supported */
+       /* Does board support performant mode? */
+       trans_support = readl(&(h->cfgtable->TransportSupport));
+       if (!(trans_support & PERFORMANT_MODE))
+               return;
+
+       printk(KERN_WARNING "cciss%d: Placing controller into "
+                               "performant mode\n", h->ctlr);
+       /* Performant mode demands commands on a 32 byte boundary
+        * pci_alloc_consistent aligns on page boundarys already.
+        * Just need to check if divisible by 32
+        */
+       if ((sizeof(CommandList_struct) % 32) != 0) {
+               printk(KERN_WARNING "%s %d %s\n",
+                       "cciss info: command size[",
+                       (int)sizeof(CommandList_struct),
+                       "] not divisible by 32, no performant mode..\n");
                return;
        }
 
+       /* Performant mode ring buffer and supporting data structures */
+       h->reply_pool = (__u64 *)pci_alloc_consistent(
+               h->pdev, h->max_commands * sizeof(__u64),
+               &(h->reply_pool_dhandle));
+
+       /* Need a block fetch table for performant mode */
+       h->blockFetchTable = kmalloc(((h->maxsgentries+1) *
+               sizeof(__u32)), GFP_KERNEL);
+
+       if ((h->reply_pool == NULL) || (h->blockFetchTable == NULL))
+               goto clean_up;
+
+       cciss_enter_performant_mode(h);
+
        /* Change the access methods to the performant access methods */
        h->access = SA5_performant_access;
+       h->transMethod = CFGTBL_Trans_Performant;
 
        return;
 clean_up:
@@ -4041,29 +4070,40 @@ static int __devinit cciss_wait_for_board_ready(ctlr_info_t *h)
        return -ENODEV;
 }
 
+static int __devinit cciss_find_cfg_addrs(struct pci_dev *pdev,
+       void __iomem *vaddr, u32 *cfg_base_addr, u64 *cfg_base_addr_index,
+       u64 *cfg_offset)
+{
+       *cfg_base_addr = readl(vaddr + SA5_CTCFG_OFFSET);
+       *cfg_offset = readl(vaddr + SA5_CTMEM_OFFSET);
+       *cfg_base_addr &= (u32) 0x0000ffff;
+       *cfg_base_addr_index = find_PCI_BAR_index(pdev, *cfg_base_addr);
+       if (*cfg_base_addr_index == -1) {
+               dev_warn(&pdev->dev, "cannot find cfg_base_addr_index, "
+                       "*cfg_base_addr = 0x%08x\n", *cfg_base_addr);
+               return -ENODEV;
+       }
+       return 0;
+}
+
 static int __devinit cciss_find_cfgtables(ctlr_info_t *h)
 {
        u64 cfg_offset;
        u32 cfg_base_addr;
        u64 cfg_base_addr_index;
        u32 trans_offset;
+       int rc;
 
-       /* get the address index number */
-       cfg_base_addr = readl(h->vaddr + SA5_CTCFG_OFFSET);
-       cfg_base_addr &= (u32) 0x0000ffff;
-       cfg_base_addr_index = find_PCI_BAR_index(h->pdev, cfg_base_addr);
-       if (cfg_base_addr_index == -1) {
-               dev_warn(&h->pdev->dev, "cannot find cfg_base_addr_index\n");
-               return -ENODEV;
-       }
-       cfg_offset = readl(h->vaddr + SA5_CTMEM_OFFSET);
+       rc = cciss_find_cfg_addrs(h->pdev, h->vaddr, &cfg_base_addr,
+               &cfg_base_addr_index, &cfg_offset);
+       if (rc)
+               return rc;
        h->cfgtable = remap_pci_mem(pci_resource_start(h->pdev,
-                              cfg_base_addr_index) + cfg_offset,
-                               sizeof(h->cfgtable));
+               cfg_base_addr_index) + cfg_offset, sizeof(h->cfgtable));
        if (!h->cfgtable)
                return -ENOMEM;
        /* Find performant mode table. */
-       trans_offset = readl(&(h->cfgtable->TransMethodOffset));
+       trans_offset = readl(&h->cfgtable->TransMethodOffset);
        h->transtable = remap_pci_mem(pci_resource_start(h->pdev,
                                cfg_base_addr_index)+cfg_offset+trans_offset,
                                sizeof(*h->transtable));
@@ -4072,6 +4112,84 @@ static int __devinit cciss_find_cfgtables(ctlr_info_t *h)
        return 0;
 }
 
+static void __devinit cciss_get_max_perf_mode_cmds(struct ctlr_info *h)
+{
+       h->max_commands = readl(&(h->cfgtable->MaxPerformantModeCommands));
+       if (h->max_commands < 16) {
+               dev_warn(&h->pdev->dev, "Controller reports "
+                       "max supported commands of %d, an obvious lie. "
+                       "Using 16.  Ensure that firmware is up to date.\n",
+                       h->max_commands);
+               h->max_commands = 16;
+       }
+}
+
+/* Interrogate the hardware for some limits:
+ * max commands, max SG elements without chaining, and with chaining,
+ * SG chain block size, etc.
+ */
+static void __devinit cciss_find_board_params(ctlr_info_t *h)
+{
+       cciss_get_max_perf_mode_cmds(h);
+       h->nr_cmds = h->max_commands - 4; /* Allow room for some ioctls */
+       h->maxsgentries = readl(&(h->cfgtable->MaxSGElements));
+       /*
+        * Limit in-command s/g elements to 32 save dma'able memory.
+        * Howvever spec says if 0, use 31
+        */
+       h->max_cmd_sgentries = 31;
+       if (h->maxsgentries > 512) {
+               h->max_cmd_sgentries = 32;
+               h->chainsize = h->maxsgentries - h->max_cmd_sgentries + 1;
+               h->maxsgentries--; /* save one for chain pointer */
+       } else {
+               h->maxsgentries = 31; /* default to traditional values */
+               h->chainsize = 0;
+       }
+}
+
+static inline bool CISS_signature_present(ctlr_info_t *h)
+{
+       if ((readb(&h->cfgtable->Signature[0]) != 'C') ||
+           (readb(&h->cfgtable->Signature[1]) != 'I') ||
+           (readb(&h->cfgtable->Signature[2]) != 'S') ||
+           (readb(&h->cfgtable->Signature[3]) != 'S')) {
+               dev_warn(&h->pdev->dev, "not a valid CISS config table\n");
+               return false;
+       }
+       return true;
+}
+
+/* Need to enable prefetch in the SCSI core for 6400 in x86 */
+static inline void cciss_enable_scsi_prefetch(ctlr_info_t *h)
+{
+#ifdef CONFIG_X86
+       u32 prefetch;
+
+       prefetch = readl(&(h->cfgtable->SCSI_Prefetch));
+       prefetch |= 0x100;
+       writel(prefetch, &(h->cfgtable->SCSI_Prefetch));
+#endif
+}
+
+/* Disable DMA prefetch for the P600.  Otherwise an ASIC bug may result
+ * in a prefetch beyond physical memory.
+ */
+static inline void cciss_p600_dma_prefetch_quirk(ctlr_info_t *h)
+{
+       u32 dma_prefetch;
+       __u32 dma_refetch;
+
+       if (h->board_id != 0x3225103C)
+               return;
+       dma_prefetch = readl(h->vaddr + I2O_DMA1_CFG);
+       dma_prefetch |= 0x8000;
+       writel(dma_prefetch, h->vaddr + I2O_DMA1_CFG);
+       pci_read_config_dword(h->pdev, PCI_COMMAND_PARITY, &dma_refetch);
+       dma_refetch |= 0x1;
+       pci_write_config_dword(h->pdev, PCI_COMMAND_PARITY, dma_refetch);
+}
+
 static int __devinit cciss_pci_init(ctlr_info_t *c)
 {
        int prod_index, err;
@@ -4124,76 +4242,15 @@ static int __devinit cciss_pci_init(ctlr_info_t *c)
        err = cciss_find_cfgtables(c);
        if (err)
                goto err_out_free_res;
-#ifdef CCISS_DEBUG
        print_cfg_table(c->cfgtable);
-#endif                         /* CCISS_DEBUG */
-
-       /* Some controllers support Zero Memory Raid (ZMR).
-        * When configured in ZMR mode the number of supported
-        * commands drops to 64. So instead of just setting an
-        * arbitrary value we make the driver a little smarter.
-        * We read the config table to tell us how many commands
-        * are supported on the controller then subtract 4 to
-        * leave a little room for ioctl calls.
-        */
-       c->max_commands = readl(&(c->cfgtable->MaxPerformantModeCommands));
-       c->maxsgentries = readl(&(c->cfgtable->MaxSGElements));
-
-       /*
-        * Limit native command to 32 s/g elements to save dma'able memory.
-        * Howvever spec says if 0, use 31
-        */
-
-       c->max_cmd_sgentries = 31;
-       if (c->maxsgentries > 512) {
-               c->max_cmd_sgentries = 32;
-               c->chainsize = c->maxsgentries - c->max_cmd_sgentries + 1;
-               c->maxsgentries -= 1;   /* account for chain pointer */
-       } else {
-               c->maxsgentries = 31;   /* Default to traditional value */
-               c->chainsize = 0;       /* traditional */
-       }
+       cciss_find_board_params(c);
 
-       c->nr_cmds = c->max_commands - 4;
-       if ((readb(&c->cfgtable->Signature[0]) != 'C') ||
-           (readb(&c->cfgtable->Signature[1]) != 'I') ||
-           (readb(&c->cfgtable->Signature[2]) != 'S') ||
-           (readb(&c->cfgtable->Signature[3]) != 'S')) {
-               printk("Does not appear to be a valid CISS config table\n");
+       if (!CISS_signature_present(c)) {
                err = -ENODEV;
                goto err_out_free_res;
        }
-#ifdef CONFIG_X86
-       {
-               /* Need to enable prefetch in the SCSI core for 6400 in x86 */
-               __u32 prefetch;
-               prefetch = readl(&(c->cfgtable->SCSI_Prefetch));
-               prefetch |= 0x100;
-               writel(prefetch, &(c->cfgtable->SCSI_Prefetch));
-       }
-#endif
-
-       /* Disabling DMA prefetch and refetch for the P600.
-        * An ASIC bug may result in accesses to invalid memory addresses.
-        * We've disabled prefetch for some time now. Testing with XEN
-        * kernels revealed a bug in the refetch if dom0 resides on a P600.
-        */
-       if (c->board_id == 0x3225103C) {
-               __u32 dma_prefetch;
-               __u32 dma_refetch;
-               dma_prefetch = readl(c->vaddr + I2O_DMA1_CFG);
-               dma_prefetch |= 0x8000;
-               writel(dma_prefetch, c->vaddr + I2O_DMA1_CFG);
-               pci_read_config_dword(c->pdev, PCI_COMMAND_PARITY,
-                       &dma_refetch);
-               dma_refetch |= 0x1;
-               pci_write_config_dword(c->pdev, PCI_COMMAND_PARITY,
-                       dma_refetch);
-       }
-
-#ifdef CCISS_DEBUG
-       printk(KERN_WARNING "Trying to put board into Performant mode\n");
-#endif                         /* CCISS_DEBUG */
+       cciss_enable_scsi_prefetch(c);
+       cciss_p600_dma_prefetch_quirk(c);
        cciss_put_controller_into_performant_mode(c);
        return 0;
 
@@ -4374,68 +4431,144 @@ static __devinit int cciss_reset_msi(struct pci_dev *pdev)
        return 0;
 }
 
-/* This does a hard reset of the controller using PCI power management
- * states. */
-static __devinit int cciss_hard_reset_controller(struct pci_dev *pdev)
+static int cciss_controller_hard_reset(struct pci_dev *pdev,
+       void * __iomem vaddr, bool use_doorbell)
 {
-       u16 pmcsr, saved_config_space[32];
-       int i, pos;
+       u16 pmcsr;
+       int pos;
 
-       printk(KERN_INFO "cciss: using PCI PM to reset controller\n");
+       if (use_doorbell) {
+               /* For everything after the P600, the PCI power state method
+                * of resetting the controller doesn't work, so we have this
+                * other way using the doorbell register.
+                */
+               dev_info(&pdev->dev, "using doorbell to reset controller\n");
+               writel(DOORBELL_CTLR_RESET, vaddr + SA5_DOORBELL);
+               msleep(1000);
+       } else { /* Try to do it the PCI power state way */
+
+               /* Quoting from the Open CISS Specification: "The Power
+                * Management Control/Status Register (CSR) controls the power
+                * state of the device.  The normal operating state is D0,
+                * CSR=00h.  The software off state is D3, CSR=03h.  To reset
+                * the controller, place the interface device in D3 then to D0,
+                * this causes a secondary PCI reset which will reset the
+                * controller." */
+
+               pos = pci_find_capability(pdev, PCI_CAP_ID_PM);
+               if (pos == 0) {
+                       dev_err(&pdev->dev,
+                               "cciss_controller_hard_reset: "
+                               "PCI PM not supported\n");
+                       return -ENODEV;
+               }
+               dev_info(&pdev->dev, "using PCI PM to reset controller\n");
+               /* enter the D3hot power management state */
+               pci_read_config_word(pdev, pos + PCI_PM_CTRL, &pmcsr);
+               pmcsr &= ~PCI_PM_CTRL_STATE_MASK;
+               pmcsr |= PCI_D3hot;
+               pci_write_config_word(pdev, pos + PCI_PM_CTRL, pmcsr);
 
-       /* This is very nearly the same thing as
+               msleep(500);
 
-          pci_save_state(pci_dev);
-          pci_set_power_state(pci_dev, PCI_D3hot);
-          pci_set_power_state(pci_dev, PCI_D0);
-          pci_restore_state(pci_dev);
+               /* enter the D0 power management state */
+               pmcsr &= ~PCI_PM_CTRL_STATE_MASK;
+               pmcsr |= PCI_D0;
+               pci_write_config_word(pdev, pos + PCI_PM_CTRL, pmcsr);
 
-          but we can't use these nice canned kernel routines on
-          kexec, because they also check the MSI/MSI-X state in PCI
-          configuration space and do the wrong thing when it is
-          set/cleared.  Also, the pci_save/restore_state functions
-          violate the ordering requirements for restoring the
-          configuration space from the CCISS document (see the
-          comment below).  So we roll our own .... */
+               msleep(500);
+       }
+       return 0;
+}
 
-       for (i = 0; i < 32; i++)
-               pci_read_config_word(pdev, 2*i, &saved_config_space[i]);
+/* This does a hard reset of the controller using PCI power management
+ * states or using the doorbell register. */
+static __devinit int cciss_kdump_hard_reset_controller(struct pci_dev *pdev)
+{
+       u16 saved_config_space[32];
+       u64 cfg_offset;
+       u32 cfg_base_addr;
+       u64 cfg_base_addr_index;
+       void __iomem *vaddr;
+       unsigned long paddr;
+       u32 misc_fw_support, active_transport;
+       int rc, i;
+       CfgTable_struct __iomem *cfgtable;
+       bool use_doorbell;
+       u32 board_id;
+
+       /* For controllers as old a the p600, this is very nearly
+        * the same thing as
+        *
+        * pci_save_state(pci_dev);
+        * pci_set_power_state(pci_dev, PCI_D3hot);
+        * pci_set_power_state(pci_dev, PCI_D0);
+        * pci_restore_state(pci_dev);
+        *
+        * but we can't use these nice canned kernel routines on
+        * kexec, because they also check the MSI/MSI-X state in PCI
+        * configuration space and do the wrong thing when it is
+        * set/cleared.  Also, the pci_save/restore_state functions
+        * violate the ordering requirements for restoring the
+        * configuration space from the CCISS document (see the
+        * comment below).  So we roll our own ....
+        *
+        * For controllers newer than the P600, the pci power state
+        * method of resetting doesn't work so we have another way
+        * using the doorbell register.
+        */
 
-       pos = pci_find_capability(pdev, PCI_CAP_ID_PM);
-       if (pos == 0) {
-               printk(KERN_ERR "cciss_reset_controller: PCI PM not supported\n");
+       /* Exclude 640x boards.  These are two pci devices in one slot
+        * which share a battery backed cache module.  One controls the
+        * cache, the other accesses the cache through the one that controls
+        * it.  If we reset the one controlling the cache, the other will
+        * likely not be happy.  Just forbid resetting this conjoined mess.
+        */
+       cciss_lookup_board_id(pdev, &board_id);
+       if (board_id == 0x409C0E11 || board_id == 0x409D0E11) {
+               dev_warn(&pdev->dev, "Cannot reset Smart Array 640x "
+                               "due to shared cache module.");
                return -ENODEV;
        }
 
-       /* Quoting from the Open CISS Specification: "The Power
-        * Management Control/Status Register (CSR) controls the power
-        * state of the device.  The normal operating state is D0,
-        * CSR=00h.  The software off state is D3, CSR=03h.  To reset
-        * the controller, place the interface device in D3 then to
-        * D0, this causes a secondary PCI reset which will reset the
-        * controller." */
+       for (i = 0; i < 32; i++)
+               pci_read_config_word(pdev, 2*i, &saved_config_space[i]);
 
-       /* enter the D3hot power management state */
-       pci_read_config_word(pdev, pos + PCI_PM_CTRL, &pmcsr);
-       pmcsr &= ~PCI_PM_CTRL_STATE_MASK;
-       pmcsr |= PCI_D3hot;
-       pci_write_config_word(pdev, pos + PCI_PM_CTRL, pmcsr);
+       /* find the first memory BAR, so we can find the cfg table */
+       rc = cciss_pci_find_memory_BAR(pdev, &paddr);
+       if (rc)
+               return rc;
+       vaddr = remap_pci_mem(paddr, 0x250);
+       if (!vaddr)
+               return -ENOMEM;
 
-       schedule_timeout_uninterruptible(HZ >> 1);
+       /* find cfgtable in order to check if reset via doorbell is supported */
+       rc = cciss_find_cfg_addrs(pdev, vaddr, &cfg_base_addr,
+                                       &cfg_base_addr_index, &cfg_offset);
+       if (rc)
+               goto unmap_vaddr;
+       cfgtable = remap_pci_mem(pci_resource_start(pdev,
+                      cfg_base_addr_index) + cfg_offset, sizeof(*cfgtable));
+       if (!cfgtable) {
+               rc = -ENOMEM;
+               goto unmap_vaddr;
+       }
 
-       /* enter the D0 power management state */
-       pmcsr &= ~PCI_PM_CTRL_STATE_MASK;
-       pmcsr |= PCI_D0;
-       pci_write_config_word(pdev, pos + PCI_PM_CTRL, pmcsr);
+       /* If reset via doorbell register is supported, use that. */
+       misc_fw_support = readl(&cfgtable->misc_fw_support);
+       use_doorbell = misc_fw_support & MISC_FW_DOORBELL_RESET;
 
-       schedule_timeout_uninterruptible(HZ >> 1);
+       rc = cciss_controller_hard_reset(pdev, vaddr, use_doorbell);
+       if (rc)
+               goto unmap_cfgtable;
 
        /* Restore the PCI configuration space.  The Open CISS
         * Specification says, "Restore the PCI Configuration
         * Registers, offsets 00h through 60h. It is important to
         * restore the command register, 16-bits at offset 04h,
         * last. Do not restore the configuration status register,
-        * 16-bits at offset 06h."  Note that the offset is 2*i. */
+        * 16-bits at offset 06h."  Note that the offset is 2*i.
+        */
        for (i = 0; i < 32; i++) {
                if (i == 2 || i == 3)
                        continue;
@@ -4444,6 +4577,63 @@ static __devinit int cciss_hard_reset_controller(struct pci_dev *pdev)
        wmb();
        pci_write_config_word(pdev, 4, saved_config_space[2]);
 
+       /* Some devices (notably the HP Smart Array 5i Controller)
+          need a little pause here */
+       msleep(CCISS_POST_RESET_PAUSE_MSECS);
+
+       /* Controller should be in simple mode at this point.  If it's not,
+        * It means we're on one of those controllers which doesn't support
+        * the doorbell reset method and on which the PCI power management reset
+        * method doesn't work (P800, for example.)
+        * In those cases, don't try to proceed, as it generally doesn't work.
+        */
+       active_transport = readl(&cfgtable->TransportActive);
+       if (active_transport & PERFORMANT_MODE) {
+               dev_warn(&pdev->dev, "Unable to successfully reset controller,"
+                       " Ignoring controller.\n");
+               rc = -ENODEV;
+       }
+
+unmap_cfgtable:
+       iounmap(cfgtable);
+
+unmap_vaddr:
+       iounmap(vaddr);
+       return rc;
+}
+
+static __devinit int cciss_init_reset_devices(struct pci_dev *pdev)
+{
+       int rc, i;
+
+       if (!reset_devices)
+               return 0;
+
+       /* Reset the controller with a PCI power-cycle or via doorbell */
+       rc = cciss_kdump_hard_reset_controller(pdev);
+
+       /* -ENOTSUPP here means we cannot reset the controller
+        * but it's already (and still) up and running in
+        * "performant mode".  Or, it might be 640x, which can't reset
+        * due to concerns about shared bbwc between 6402/6404 pair.
+        */
+       if (rc == -ENOTSUPP)
+               return 0; /* just try to do the kdump anyhow. */
+       if (rc)
+               return -ENODEV;
+       if (cciss_reset_msi(pdev))
+               return -ENODEV;
+
+       /* Now try to get the controller to respond to a no-op */
+       for (i = 0; i < CCISS_POST_RESET_NOOP_RETRIES; i++) {
+               if (cciss_noop(pdev) == 0)
+                       break;
+               else
+                       dev_warn(&pdev->dev, "no-op failed%s\n",
+                               (i < CCISS_POST_RESET_NOOP_RETRIES - 1 ?
+                                       "; re-trying" : ""));
+               msleep(CCISS_POST_RESET_NOOP_INTERVAL_MSECS);
+       }
        return 0;
 }
 
@@ -4462,26 +4652,9 @@ static int __devinit cciss_init_one(struct pci_dev *pdev,
        int dac, return_code;
        InquiryData_struct *inq_buff;
 
-       if (reset_devices) {
-               /* Reset the controller with a PCI power-cycle */
-               if (cciss_hard_reset_controller(pdev) || cciss_reset_msi(pdev))
-                       return -ENODEV;
-
-               /* Now try to get the controller to respond to a no-op. Some
-                  devices (notably the HP Smart Array 5i Controller) need
-                  up to 30 seconds to respond. */
-               for (i=0; i<30; i++) {
-                       if (cciss_noop(pdev) == 0)
-                               break;
-
-                       schedule_timeout_uninterruptible(HZ);
-               }
-               if (i == 30) {
-                       printk(KERN_ERR "cciss: controller seems dead\n");
-                       return -EBUSY;
-               }
-       }
-
+       rc = cciss_init_reset_devices(pdev);
+       if (rc)
+               return rc;
        i = alloc_cciss_hba();
        if (i < 0)
                return -1;