iwlwifi: split fw-error-dump between transport and mvm
[deliverable/linux.git] / drivers / net / wireless / iwlwifi / pcie / trans.c
index 788085bc65d78e3382c7fa76ca74de30abd4cd84..a90292c793427de853ecf868fd8200c6ad71c9f9 100644 (file)
@@ -67,6 +67,7 @@
 #include <linux/sched.h>
 #include <linux/bitops.h>
 #include <linux/gfp.h>
+#include <linux/vmalloc.h>
 
 #include "iwl-drv.h"
 #include "iwl-trans.h"
 #include "iwl-fw-error-dump.h"
 #include "internal.h"
 
+static void iwl_pcie_free_fw_monitor(struct iwl_trans *trans)
+{
+       struct iwl_trans_pcie *trans_pcie = IWL_TRANS_GET_PCIE_TRANS(trans);
+
+       if (!trans_pcie->fw_mon_page)
+               return;
+
+       dma_unmap_page(trans->dev, trans_pcie->fw_mon_phys,
+                      trans_pcie->fw_mon_size, DMA_FROM_DEVICE);
+       __free_pages(trans_pcie->fw_mon_page,
+                    get_order(trans_pcie->fw_mon_size));
+       trans_pcie->fw_mon_page = NULL;
+       trans_pcie->fw_mon_phys = 0;
+       trans_pcie->fw_mon_size = 0;
+}
+
+static void iwl_pcie_alloc_fw_monitor(struct iwl_trans *trans)
+{
+       struct iwl_trans_pcie *trans_pcie = IWL_TRANS_GET_PCIE_TRANS(trans);
+       struct page *page;
+       dma_addr_t phys;
+       u32 size;
+       u8 power;
+
+       if (trans_pcie->fw_mon_page) {
+               dma_sync_single_for_device(trans->dev, trans_pcie->fw_mon_phys,
+                                          trans_pcie->fw_mon_size,
+                                          DMA_FROM_DEVICE);
+               return;
+       }
+
+       phys = 0;
+       for (power = 26; power >= 11; power--) {
+               int order;
+
+               size = BIT(power);
+               order = get_order(size);
+               page = alloc_pages(__GFP_COMP | __GFP_NOWARN | __GFP_ZERO,
+                                  order);
+               if (!page)
+                       continue;
+
+               phys = dma_map_page(trans->dev, page, 0, PAGE_SIZE << order,
+                                   DMA_FROM_DEVICE);
+               if (dma_mapping_error(trans->dev, phys)) {
+                       __free_pages(page, order);
+                       continue;
+               }
+               IWL_INFO(trans,
+                        "Allocated 0x%08x bytes (order %d) for firmware monitor.\n",
+                        size, order);
+               break;
+       }
+
+       if (!page)
+               return;
+
+       trans_pcie->fw_mon_page = page;
+       trans_pcie->fw_mon_phys = phys;
+       trans_pcie->fw_mon_size = size;
+}
+
 static u32 iwl_trans_pcie_read_shr(struct iwl_trans *trans, u32 reg)
 {
        iwl_write32(trans, HEEP_CTRL_WRD_PCIEX_CTRL_REG,
@@ -675,6 +738,7 @@ static int iwl_pcie_load_cpu_sections(struct iwl_trans *trans,
 static int iwl_pcie_load_given_ucode(struct iwl_trans *trans,
                                const struct fw_img *image)
 {
+       struct iwl_trans_pcie *trans_pcie = IWL_TRANS_GET_PCIE_TRANS(trans);
        int ret = 0;
        int first_ucode_section;
 
@@ -733,6 +797,20 @@ static int iwl_pcie_load_given_ucode(struct iwl_trans *trans,
                        return ret;
        }
 
+       /* supported for 7000 only for the moment */
+       if (iwlwifi_mod_params.fw_monitor &&
+           trans->cfg->device_family == IWL_DEVICE_FAMILY_7000) {
+               iwl_pcie_alloc_fw_monitor(trans);
+
+               if (trans_pcie->fw_mon_size) {
+                       iwl_write_prph(trans, MON_BUFF_BASE_ADDR,
+                                      trans_pcie->fw_mon_phys >> 4);
+                       iwl_write_prph(trans, MON_BUFF_END_ADDR,
+                                      (trans_pcie->fw_mon_phys +
+                                       trans_pcie->fw_mon_size) >> 4);
+               }
+       }
+
        /* release CPU reset */
        if (trans->cfg->device_family == IWL_DEVICE_FAMILY_8000)
                iwl_write_prph(trans, RELEASE_CPU_RESET, RELEASE_CPU_RESET_BIT);
@@ -1126,6 +1204,8 @@ void iwl_trans_pcie_free(struct iwl_trans *trans)
        if (trans_pcie->napi.poll)
                netif_napi_del(&trans_pcie->napi);
 
+       iwl_pcie_free_fw_monitor(trans);
+
        kfree(trans);
 }
 
@@ -1494,10 +1574,12 @@ static ssize_t iwl_dbgfs_tx_queue_read(struct file *file,
                txq = &trans_pcie->txq[cnt];
                q = &txq->q;
                pos += scnprintf(buf + pos, bufsz - pos,
-                               "hwq %.2d: read=%u write=%u use=%d stop=%d\n",
+                               "hwq %.2d: read=%u write=%u use=%d stop=%d need_update=%d%s\n",
                                cnt, q->read_ptr, q->write_ptr,
                                !!test_bit(cnt, trans_pcie->queue_used),
-                               !!test_bit(cnt, trans_pcie->queue_stopped));
+                                !!test_bit(cnt, trans_pcie->queue_stopped),
+                                txq->need_update,
+                                (cnt == trans_pcie->cmd_queue ? " HCMD" : ""));
        }
        ret = simple_read_from_buffer(user_buf, count, ppos, buf, pos);
        kfree(buf);
@@ -1519,6 +1601,10 @@ static ssize_t iwl_dbgfs_rx_queue_read(struct file *file,
                                                rxq->read);
        pos += scnprintf(buf + pos, bufsz - pos, "write: %u\n",
                                                rxq->write);
+       pos += scnprintf(buf + pos, bufsz - pos, "write_actual: %u\n",
+                                               rxq->write_actual);
+       pos += scnprintf(buf + pos, bufsz - pos, "need_update: %d\n",
+                                               rxq->need_update);
        pos += scnprintf(buf + pos, bufsz - pos, "free_count: %u\n",
                                                rxq->free_count);
        if (rxq->rb_stts) {
@@ -1688,23 +1774,30 @@ static u32 iwl_trans_pcie_get_cmdlen(struct iwl_tfd *tfd)
        return cmdlen;
 }
 
-static u32 iwl_trans_pcie_dump_data(struct iwl_trans *trans,
-                                   void *buf, u32 buflen)
+static
+struct iwl_trans_dump_data *iwl_trans_pcie_dump_data(struct iwl_trans *trans)
 {
        struct iwl_trans_pcie *trans_pcie = IWL_TRANS_GET_PCIE_TRANS(trans);
        struct iwl_fw_error_dump_data *data;
        struct iwl_txq *cmdq = &trans_pcie->txq[trans_pcie->cmd_queue];
        struct iwl_fw_error_dump_txcmd *txcmd;
+       struct iwl_trans_dump_data *dump_data;
        u32 len;
        int i, ptr;
 
-       if (!buf)
-               return sizeof(*data) +
-                      cmdq->q.n_window * (sizeof(*txcmd) +
-                                          TFD_MAX_PAYLOAD_SIZE);
+       len = sizeof(*dump_data) + sizeof(*data) +
+               cmdq->q.n_window * (sizeof(*txcmd) + TFD_MAX_PAYLOAD_SIZE);
+
+       if (trans_pcie->fw_mon_page)
+               len += sizeof(*data) + sizeof(struct iwl_fw_error_dump_fw_mon) +
+                       trans_pcie->fw_mon_size;
+
+       dump_data = vzalloc(len);
+       if (!dump_data)
+               return NULL;
 
        len = 0;
-       data = buf;
+       data = (void *)dump_data->data;
        data->type = cpu_to_le32(IWL_FW_ERROR_DUMP_TXCMD);
        txcmd = (void *)data->data;
        spin_lock_bh(&cmdq->lock);
@@ -1729,7 +1822,42 @@ static u32 iwl_trans_pcie_dump_data(struct iwl_trans *trans,
        spin_unlock_bh(&cmdq->lock);
 
        data->len = cpu_to_le32(len);
-       return sizeof(*data) + len;
+       len += sizeof(*data);
+
+       if (trans_pcie->fw_mon_page) {
+               struct iwl_fw_error_dump_fw_mon *fw_mon_data;
+
+               data = iwl_fw_error_next_data(data);
+               data->type = cpu_to_le32(IWL_FW_ERROR_DUMP_FW_MONITOR);
+               data->len = cpu_to_le32(trans_pcie->fw_mon_size +
+                                       sizeof(*fw_mon_data));
+               fw_mon_data = (void *)data->data;
+               fw_mon_data->fw_mon_wr_ptr =
+                       cpu_to_le32(iwl_read_prph(trans, MON_BUFF_WRPTR));
+               fw_mon_data->fw_mon_cycle_cnt =
+                       cpu_to_le32(iwl_read_prph(trans, MON_BUFF_CYCLE_CNT));
+               fw_mon_data->fw_mon_base_ptr =
+                       cpu_to_le32(iwl_read_prph(trans, MON_BUFF_BASE_ADDR));
+
+               /*
+                * The firmware is now asserted, it won't write anything to
+                * the buffer. CPU can take ownership to fetch the data.
+                * The buffer will be handed back to the device before the
+                * firmware will be restarted.
+                */
+               dma_sync_single_for_cpu(trans->dev, trans_pcie->fw_mon_phys,
+                                       trans_pcie->fw_mon_size,
+                                       DMA_FROM_DEVICE);
+               memcpy(fw_mon_data->data, page_address(trans_pcie->fw_mon_page),
+                      trans_pcie->fw_mon_size);
+
+               len += sizeof(*data) + sizeof(*fw_mon_data) +
+                       trans_pcie->fw_mon_size;
+       }
+
+       dump_data->len = len;
+
+       return dump_data;
 }
 #else
 static int iwl_trans_pcie_dbgfs_register(struct iwl_trans *trans,
@@ -1870,6 +1998,16 @@ struct iwl_trans *iwl_trans_pcie_alloc(struct pci_dev *pdev,
        }
 
        trans->hw_rev = iwl_read32(trans, CSR_HW_REV);
+       /*
+        * In the 8000 HW family the format of the 4 bytes of CSR_HW_REV have
+        * changed, and now the revision step also includes bit 0-1 (no more
+        * "dash" value). To keep hw_rev backwards compatible - we'll store it
+        * in the old format.
+        */
+       if (trans->cfg->device_family == IWL_DEVICE_FAMILY_8000)
+               trans->hw_rev = (trans->hw_rev & 0xfff0) |
+                               ((trans->hw_rev << 2) & 0xc);
+
        trans->hw_id = (pdev->device << 16) + pdev->subsystem_device;
        snprintf(trans->hw_id_str, sizeof(trans->hw_id_str),
                 "PCI ID: 0x%04X:0x%04X", pdev->device, pdev->subsystem_device);
This page took 0.028234 seconds and 5 git commands to generate.