qlcnic: Add support for PEX DMA method to read memory section of adapter dump
authorShahed Shaikh <shahed.shaikh@qlogic.com>
Sat, 22 Jun 2013 08:12:03 +0000 (04:12 -0400)
committerDavid S. Miller <davem@davemloft.net>
Mon, 24 Jun 2013 01:29:58 +0000 (18:29 -0700)
This patch adds support to read memory section of adapter
dump using PEX DMA method. This method significantly improves
total adapter dump collection time.

Signed-off-by: Shahed Shaikh <shahed.shaikh@qlogic.com>
Signed-off-by: Jitendra Kalsaria <jitendra.kalsaria@qlogic.com>
Signed-off-by: David S. Miller <davem@davemloft.net>
drivers/net/ethernet/qlogic/qlcnic/qlcnic.h
drivers/net/ethernet/qlogic/qlcnic/qlcnic_minidump.c

index 28a1276..cc2c2c1 100644 (file)
@@ -393,6 +393,9 @@ struct qlcnic_fw_dump {
        u32     size;   /* total size of the dump */
        void    *data;  /* dump data area */
        struct  qlcnic_dump_template_hdr *tmpl_hdr;
+       dma_addr_t phys_addr;
+       void    *dma_buffer;
+       bool    use_pex_dma;
 };
 
 /*
index 4b9bab1..ab8a674 100644 (file)
@@ -15,6 +15,7 @@
 #define QLC_83XX_MINIDUMP_FLASH                0x520000
 #define QLC_83XX_OCM_INDEX                     3
 #define QLC_83XX_PCI_INDEX                     0
+#define QLC_83XX_DMA_ENGINE_INDEX              8
 
 static const u32 qlcnic_ms_read_data[] = {
        0x410000A8, 0x410000AC, 0x410000B8, 0x410000BC
@@ -32,6 +33,16 @@ static const u32 qlcnic_ms_read_data[] = {
 
 #define QLCNIC_DUMP_MASK_MAX   0xff
 
+struct qlcnic_pex_dma_descriptor {
+       u32     read_data_size;
+       u32     dma_desc_cmd;
+       u32     src_addr_low;
+       u32     src_addr_high;
+       u32     dma_bus_addr_low;
+       u32     dma_bus_addr_high;
+       u32     rsvd[6];
+} __packed;
+
 struct qlcnic_common_entry_hdr {
        u32     type;
        u32     offset;
@@ -90,7 +101,10 @@ struct __ocm {
 } __packed;
 
 struct __mem {
-       u8      rsvd[24];
+       u32     desc_card_addr;
+       u32     dma_desc_cmd;
+       u32     start_dma_cmd;
+       u32     rsvd[3];
        u32     addr;
        u32     size;
 } __packed;
@@ -466,12 +480,12 @@ skip_poll:
        return l2->no_ops * l2->read_addr_num * sizeof(u32);
 }
 
-static u32 qlcnic_read_memory(struct qlcnic_adapter *adapter,
-                             struct qlcnic_dump_entry *entry, __le32 *buffer)
+static u32 qlcnic_read_memory_test_agent(struct qlcnic_adapter *adapter,
+                                        struct __mem *mem, __le32 *buffer,
+                                        int *ret)
 {
-       u32 addr, data, test, ret = 0;
+       u32 addr, data, test;
        int i, reg_read;
-       struct __mem *mem = &entry->region.mem;
 
        reg_read = mem->size;
        addr = mem->addr;
@@ -480,7 +494,8 @@ static u32 qlcnic_read_memory(struct qlcnic_adapter *adapter,
                dev_info(&adapter->pdev->dev,
                         "Unaligned memory addr:0x%x size:0x%x\n",
                         addr, reg_read);
-               return -EINVAL;
+               *ret = -EINVAL;
+               return 0;
        }
 
        mutex_lock(&adapter->ahw->mem_lock);
@@ -499,7 +514,7 @@ static u32 qlcnic_read_memory(struct qlcnic_adapter *adapter,
                        if (printk_ratelimit()) {
                                dev_err(&adapter->pdev->dev,
                                        "failed to read through agent\n");
-                               ret = -EINVAL;
+                               *ret = -EIO;
                                goto out;
                        }
                }
@@ -516,6 +531,181 @@ out:
        return mem->size;
 }
 
+/* DMA register base address */
+#define QLC_DMA_REG_BASE_ADDR(dma_no)  (0x77320000 + (dma_no * 0x10000))
+
+/* DMA register offsets w.r.t base address */
+#define QLC_DMA_CMD_BUFF_ADDR_LOW      0
+#define QLC_DMA_CMD_BUFF_ADDR_HI       4
+#define QLC_DMA_CMD_STATUS_CTRL                8
+
+#define QLC_PEX_DMA_READ_SIZE          (PAGE_SIZE * 16)
+
+static int qlcnic_start_pex_dma(struct qlcnic_adapter *adapter,
+                               struct __mem *mem)
+{
+       struct qlcnic_dump_template_hdr *tmpl_hdr;
+       struct device *dev = &adapter->pdev->dev;
+       u32 dma_no, dma_base_addr, temp_addr;
+       int i, ret, dma_sts;
+
+       tmpl_hdr = adapter->ahw->fw_dump.tmpl_hdr;
+       dma_no = tmpl_hdr->saved_state[QLC_83XX_DMA_ENGINE_INDEX];
+       dma_base_addr = QLC_DMA_REG_BASE_ADDR(dma_no);
+
+       temp_addr = dma_base_addr + QLC_DMA_CMD_BUFF_ADDR_LOW;
+       ret = qlcnic_83xx_wrt_reg_indirect(adapter, temp_addr,
+                                          mem->desc_card_addr);
+       if (ret)
+               return ret;
+
+       temp_addr = dma_base_addr + QLC_DMA_CMD_BUFF_ADDR_HI;
+       ret = qlcnic_83xx_wrt_reg_indirect(adapter, temp_addr, 0);
+       if (ret)
+               return ret;
+
+       temp_addr = dma_base_addr + QLC_DMA_CMD_STATUS_CTRL;
+       ret = qlcnic_83xx_wrt_reg_indirect(adapter, temp_addr,
+                                          mem->start_dma_cmd);
+       if (ret)
+               return ret;
+
+       /* Wait for DMA to complete */
+       temp_addr = dma_base_addr + QLC_DMA_CMD_STATUS_CTRL;
+       for (i = 0; i < 400; i++) {
+               dma_sts = qlcnic_ind_rd(adapter, temp_addr);
+
+               if (dma_sts & BIT_1)
+                       usleep_range(250, 500);
+               else
+                       break;
+       }
+
+       if (i >= 400) {
+               dev_info(dev, "PEX DMA operation timed out");
+               ret = -EIO;
+       }
+
+       return ret;
+}
+
+static u32 qlcnic_read_memory_pexdma(struct qlcnic_adapter *adapter,
+                                    struct __mem *mem,
+                                    __le32 *buffer, int *ret)
+{
+       struct qlcnic_fw_dump *fw_dump = &adapter->ahw->fw_dump;
+       u32 temp, dma_base_addr, size = 0, read_size = 0;
+       struct qlcnic_pex_dma_descriptor *dma_descr;
+       struct qlcnic_dump_template_hdr *tmpl_hdr;
+       struct device *dev = &adapter->pdev->dev;
+       dma_addr_t dma_phys_addr;
+       void *dma_buffer;
+
+       tmpl_hdr = fw_dump->tmpl_hdr;
+
+       /* Check if DMA engine is available */
+       temp = tmpl_hdr->saved_state[QLC_83XX_DMA_ENGINE_INDEX];
+       dma_base_addr = QLC_DMA_REG_BASE_ADDR(temp);
+       temp = qlcnic_ind_rd(adapter,
+                            dma_base_addr + QLC_DMA_CMD_STATUS_CTRL);
+
+       if (!(temp & BIT_31)) {
+               dev_info(dev, "%s: DMA engine is not available\n", __func__);
+               *ret = -EIO;
+               return 0;
+       }
+
+       /* Create DMA descriptor */
+       dma_descr = kzalloc(sizeof(struct qlcnic_pex_dma_descriptor),
+                           GFP_KERNEL);
+       if (!dma_descr) {
+               *ret = -ENOMEM;
+               return 0;
+       }
+
+       /* dma_desc_cmd  0:15  = 0
+        * dma_desc_cmd 16:19  = mem->dma_desc_cmd 0:3
+        * dma_desc_cmd 20:23  = pci function number
+        * dma_desc_cmd 24:31  = mem->dma_desc_cmd 8:15
+        */
+       dma_phys_addr = fw_dump->phys_addr;
+       dma_buffer = fw_dump->dma_buffer;
+       temp = 0;
+       temp = mem->dma_desc_cmd & 0xff0f;
+       temp |= (adapter->ahw->pci_func & 0xf) << 4;
+       dma_descr->dma_desc_cmd = (temp << 16) & 0xffff0000;
+       dma_descr->dma_bus_addr_low = LSD(dma_phys_addr);
+       dma_descr->dma_bus_addr_high = MSD(dma_phys_addr);
+       dma_descr->src_addr_high = 0;
+
+       /* Collect memory dump using multiple DMA operations if required */
+       while (read_size < mem->size) {
+               if (mem->size - read_size >= QLC_PEX_DMA_READ_SIZE)
+                       size = QLC_PEX_DMA_READ_SIZE;
+               else
+                       size = mem->size - read_size;
+
+               dma_descr->src_addr_low = mem->addr + read_size;
+               dma_descr->read_data_size = size;
+
+               /* Write DMA descriptor to MS memory*/
+               temp = sizeof(struct qlcnic_pex_dma_descriptor) / 16;
+               *ret = qlcnic_83xx_ms_mem_write128(adapter, mem->desc_card_addr,
+                                                  (u32 *)dma_descr, temp);
+               if (*ret) {
+                       dev_info(dev, "Failed to write DMA descriptor to MS memory at address 0x%x\n",
+                                mem->desc_card_addr);
+                       goto free_dma_descr;
+               }
+
+               *ret = qlcnic_start_pex_dma(adapter, mem);
+               if (*ret) {
+                       dev_info(dev, "Failed to start PEX DMA operation\n");
+                       goto free_dma_descr;
+               }
+
+               memcpy(buffer, dma_buffer, size);
+               buffer += size / 4;
+               read_size += size;
+       }
+
+free_dma_descr:
+       kfree(dma_descr);
+
+       return read_size;
+}
+
+static u32 qlcnic_read_memory(struct qlcnic_adapter *adapter,
+                             struct qlcnic_dump_entry *entry, __le32 *buffer)
+{
+       struct qlcnic_fw_dump *fw_dump = &adapter->ahw->fw_dump;
+       struct device *dev = &adapter->pdev->dev;
+       struct __mem *mem = &entry->region.mem;
+       u32 data_size;
+       int ret = 0;
+
+       if (fw_dump->use_pex_dma) {
+               data_size = qlcnic_read_memory_pexdma(adapter, mem, buffer,
+                                                     &ret);
+               if (ret)
+                       dev_info(dev,
+                                "Failed to read memory dump using PEX DMA: mask[0x%x]\n",
+                                entry->hdr.mask);
+               else
+                       return data_size;
+       }
+
+       data_size = qlcnic_read_memory_test_agent(adapter, mem, buffer, &ret);
+       if (ret) {
+               dev_info(dev,
+                        "Failed to read memory dump using test agent method: mask[0x%x]\n",
+                        entry->hdr.mask);
+               return 0;
+       } else {
+               return data_size;
+       }
+}
+
 static u32 qlcnic_dump_nop(struct qlcnic_adapter *adapter,
                           struct qlcnic_dump_entry *entry, __le32 *buffer)
 {
@@ -893,6 +1083,12 @@ flash_temp:
 
        tmpl_hdr = ahw->fw_dump.tmpl_hdr;
        tmpl_hdr->drv_cap_mask = QLCNIC_DUMP_MASK_DEF;
+
+       if ((tmpl_hdr->version & 0xffffff) >= 0x20001)
+               ahw->fw_dump.use_pex_dma = true;
+       else
+               ahw->fw_dump.use_pex_dma = false;
+
        ahw->fw_dump.enable = 1;
 
        return 0;
@@ -910,7 +1106,9 @@ int qlcnic_dump_fw(struct qlcnic_adapter *adapter)
        struct qlcnic_fw_dump *fw_dump = &adapter->ahw->fw_dump;
        struct qlcnic_dump_template_hdr *tmpl_hdr = fw_dump->tmpl_hdr;
        static const struct qlcnic_dump_operations *fw_dump_ops;
+       struct device *dev = &adapter->pdev->dev;
        struct qlcnic_hardware_context *ahw;
+       void *temp_buffer;
 
        ahw = adapter->ahw;
 
@@ -944,6 +1142,16 @@ int qlcnic_dump_fw(struct qlcnic_adapter *adapter)
        tmpl_hdr->sys_info[0] = QLCNIC_DRIVER_VERSION;
        tmpl_hdr->sys_info[1] = adapter->fw_version;
 
+       if (fw_dump->use_pex_dma) {
+               temp_buffer = dma_alloc_coherent(dev, QLC_PEX_DMA_READ_SIZE,
+                                                &fw_dump->phys_addr,
+                                                GFP_KERNEL);
+               if (!temp_buffer)
+                       fw_dump->use_pex_dma = false;
+               else
+                       fw_dump->dma_buffer = temp_buffer;
+       }
+
        if (qlcnic_82xx_check(adapter)) {
                ops_cnt = ARRAY_SIZE(qlcnic_fw_dump_ops);
                fw_dump_ops = qlcnic_fw_dump_ops;
@@ -1002,6 +1210,9 @@ int qlcnic_dump_fw(struct qlcnic_adapter *adapter)
                return 0;
        }
 error:
+       if (fw_dump->use_pex_dma)
+               dma_free_coherent(dev, QLC_PEX_DMA_READ_SIZE,
+                                 fw_dump->dma_buffer, fw_dump->phys_addr);
        vfree(fw_dump->data);
        return -EINVAL;
 }