]> asedeno.scripts.mit.edu Git - linux.git/commitdiff
iwlwifi: pcie: extend hardware workaround to context-info
authorJohannes Berg <johannes.berg@intel.com>
Thu, 5 Dec 2019 08:31:07 +0000 (09:31 +0100)
committerLuca Coelho <luciano.coelho@intel.com>
Mon, 23 Dec 2019 09:33:04 +0000 (11:33 +0200)
After more investigation on the hardware side, it appears that the
hardware bug regarding 2^32 boundary reaching/crossing also affects
other uses of the DMA engine, in particular the ones triggered by
the context-info (image loader) mechanism.

It also turns out that the bug only affects devices with gen2 TX
hardware engine, so we don't need to change context info for gen3.
The TX path workarounds are simpler to still keep for both though.

Add the workaround to that code as well; this is a lot simpler as
we have just a single way to allocate DMA memory there.

I made the algorithm recursive (with a small limit) since it's
actually (almost) impossible to hit this today - dma_alloc_coherent
is currently documented to always return 32-bit addressable memory
regardless of the DMA mask for it, and so we could only get REALLY
unlucky to get the very last page in that area.

Signed-off-by: Johannes Berg <johannes.berg@intel.com>
Signed-off-by: Luca Coelho <luciano.coelho@intel.com>
drivers/net/wireless/intel/iwlwifi/pcie/ctxt-info.c
drivers/net/wireless/intel/iwlwifi/pcie/internal.h
drivers/net/wireless/intel/iwlwifi/pcie/tx-gen2.c

index d38cefbb779e8e16dcbecb208c2111441b9920e4..e249e3fd14c609c7c135b059dceedaa78f44d532 100644 (file)
 #include "internal.h"
 #include "iwl-prph.h"
 
+static void *_iwl_pcie_ctxt_info_dma_alloc_coherent(struct iwl_trans *trans,
+                                                   size_t size,
+                                                   dma_addr_t *phys,
+                                                   int depth)
+{
+       void *result;
+
+       if (WARN(depth > 2,
+                "failed to allocate DMA memory not crossing 2^32 boundary"))
+               return NULL;
+
+       result = dma_alloc_coherent(trans->dev, size, phys, GFP_KERNEL);
+
+       if (!result)
+               return NULL;
+
+       if (unlikely(iwl_pcie_crosses_4g_boundary(*phys, size))) {
+               void *old = result;
+               dma_addr_t oldphys = *phys;
+
+               result = _iwl_pcie_ctxt_info_dma_alloc_coherent(trans, size,
+                                                               phys,
+                                                               depth + 1);
+               dma_free_coherent(trans->dev, size, old, oldphys);
+       }
+
+       return result;
+}
+
+static void *iwl_pcie_ctxt_info_dma_alloc_coherent(struct iwl_trans *trans,
+                                                  size_t size,
+                                                  dma_addr_t *phys)
+{
+       return _iwl_pcie_ctxt_info_dma_alloc_coherent(trans, size, phys, 0);
+}
+
 void iwl_pcie_ctxt_info_free_paging(struct iwl_trans *trans)
 {
        struct iwl_self_init_dram *dram = &trans->init_dram;
@@ -161,14 +197,17 @@ int iwl_pcie_ctxt_info_init(struct iwl_trans *trans,
        struct iwl_context_info *ctxt_info;
        struct iwl_context_info_rbd_cfg *rx_cfg;
        u32 control_flags = 0, rb_size;
+       dma_addr_t phys;
        int ret;
 
-       ctxt_info = dma_alloc_coherent(trans->dev, sizeof(*ctxt_info),
-                                      &trans_pcie->ctxt_info_dma_addr,
-                                      GFP_KERNEL);
+       ctxt_info = iwl_pcie_ctxt_info_dma_alloc_coherent(trans,
+                                                         sizeof(*ctxt_info),
+                                                         &phys);
        if (!ctxt_info)
                return -ENOMEM;
 
+       trans_pcie->ctxt_info_dma_addr = phys;
+
        ctxt_info->version.version = 0;
        ctxt_info->version.mac_id =
                cpu_to_le16((u16)iwl_read32(trans, CSR_HW_REV));
index 04361ecf31bd5c56328251c58cba2890b0c2bd41..f14bcef3495e21a4c9ca436cae8ad2c58ccc3455 100644 (file)
@@ -672,6 +672,16 @@ void iwl_pcie_disable_ict(struct iwl_trans *trans);
 /*****************************************************
 * TX / HCMD
 ******************************************************/
+/*
+ * We need this inline in case dma_addr_t is only 32-bits - since the
+ * hardware is always 64-bit, the issue can still occur in that case,
+ * so use u64 for 'phys' here to force the addition in 64-bit.
+ */
+static inline bool iwl_pcie_crosses_4g_boundary(u64 phys, u16 len)
+{
+       return upper_32_bits(phys) != upper_32_bits(phys + len);
+}
+
 int iwl_pcie_tx_init(struct iwl_trans *trans);
 int iwl_pcie_gen2_tx_init(struct iwl_trans *trans, int txq_id,
                          int queue_size);
index 56d752beb940f98ac6817e8313d4e618e51f49fa..bfb984b2e00c6079b36334eb5a611a0206fa843a 100644 (file)
@@ -213,16 +213,6 @@ static void iwl_pcie_gen2_free_tfd(struct iwl_trans *trans, struct iwl_txq *txq)
        }
 }
 
-/*
- * We need this inline in case dma_addr_t is only 32-bits - since the
- * hardware is always 64-bit, the issue can still occur in that case,
- * so use u64 for 'phys' here to force the addition in 64-bit.
- */
-static inline bool crosses_4g_boundary(u64 phys, u16 len)
-{
-       return upper_32_bits(phys) != upper_32_bits(phys + len);
-}
-
 static int iwl_pcie_gen2_set_tb(struct iwl_trans *trans,
                                struct iwl_tfh_tfd *tfd, dma_addr_t addr,
                                u16 len)
@@ -238,7 +228,7 @@ static int iwl_pcie_gen2_set_tb(struct iwl_trans *trans,
         * there's no more space, and so when we know there is enough we
         * don't always check ...
         */
-       WARN(crosses_4g_boundary(addr, len),
+       WARN(iwl_pcie_crosses_4g_boundary(addr, len),
             "possible DMA problem with iova:0x%llx, len:%d\n",
             (unsigned long long)addr, len);
 
@@ -300,7 +290,7 @@ static int iwl_pcie_gen2_set_tb_with_wa(struct iwl_trans *trans,
        if (unlikely(dma_mapping_error(trans->dev, phys)))
                return -ENOMEM;
 
-       if (likely(!crosses_4g_boundary(phys, len))) {
+       if (likely(!iwl_pcie_crosses_4g_boundary(phys, len))) {
                ret = iwl_pcie_gen2_set_tb(trans, tfd, phys, len);
 
                if (ret < 0)