iommu/vt-d: Report more information about invalidation errors
authorLu Baolu <baolu.lu@linux.intel.com>
Thu, 18 Mar 2021 00:53:40 +0000 (08:53 +0800)
committerJoerg Roedel <jroedel@suse.de>
Thu, 18 Mar 2021 10:27:46 +0000 (11:27 +0100)
When the invalidation queue errors are encountered, dump the information
logged by the VT-d hardware together with the pending queue invalidation
descriptors.

Signed-off-by: Ashok Raj <ashok.raj@intel.com>
Tested-by: Guo Kaijie <Kaijie.Guo@intel.com>
Signed-off-by: Lu Baolu <baolu.lu@linux.intel.com>
Link: https://lore.kernel.org/r/20210318005340.187311-1-baolu.lu@linux.intel.com
Signed-off-by: Joerg Roedel <jroedel@suse.de>
drivers/iommu/intel/dmar.c
include/linux/intel-iommu.h

index d5c51b5..6971397 100644 (file)
@@ -1205,6 +1205,63 @@ static inline void reclaim_free_desc(struct q_inval *qi)
        }
 }
 
+static const char *qi_type_string(u8 type)
+{
+       switch (type) {
+       case QI_CC_TYPE:
+               return "Context-cache Invalidation";
+       case QI_IOTLB_TYPE:
+               return "IOTLB Invalidation";
+       case QI_DIOTLB_TYPE:
+               return "Device-TLB Invalidation";
+       case QI_IEC_TYPE:
+               return "Interrupt Entry Cache Invalidation";
+       case QI_IWD_TYPE:
+               return "Invalidation Wait";
+       case QI_EIOTLB_TYPE:
+               return "PASID-based IOTLB Invalidation";
+       case QI_PC_TYPE:
+               return "PASID-cache Invalidation";
+       case QI_DEIOTLB_TYPE:
+               return "PASID-based Device-TLB Invalidation";
+       case QI_PGRP_RESP_TYPE:
+               return "Page Group Response";
+       default:
+               return "UNKNOWN";
+       }
+}
+
+static void qi_dump_fault(struct intel_iommu *iommu, u32 fault)
+{
+       unsigned int head = dmar_readl(iommu->reg + DMAR_IQH_REG);
+       u64 iqe_err = dmar_readq(iommu->reg + DMAR_IQER_REG);
+       struct qi_desc *desc = iommu->qi->desc + head;
+
+       if (fault & DMA_FSTS_IQE)
+               pr_err("VT-d detected Invalidation Queue Error: Reason %llx",
+                      DMAR_IQER_REG_IQEI(iqe_err));
+       if (fault & DMA_FSTS_ITE)
+               pr_err("VT-d detected Invalidation Time-out Error: SID %llx",
+                      DMAR_IQER_REG_ITESID(iqe_err));
+       if (fault & DMA_FSTS_ICE)
+               pr_err("VT-d detected Invalidation Completion Error: SID %llx",
+                      DMAR_IQER_REG_ICESID(iqe_err));
+
+       pr_err("QI HEAD: %s qw0 = 0x%llx, qw1 = 0x%llx\n",
+              qi_type_string(desc->qw0 & 0xf),
+              (unsigned long long)desc->qw0,
+              (unsigned long long)desc->qw1);
+
+       head = ((head >> qi_shift(iommu)) + QI_LENGTH - 1) % QI_LENGTH;
+       head <<= qi_shift(iommu);
+       desc = iommu->qi->desc + head;
+
+       pr_err("QI PRIOR: %s qw0 = 0x%llx, qw1 = 0x%llx\n",
+              qi_type_string(desc->qw0 & 0xf),
+              (unsigned long long)desc->qw0,
+              (unsigned long long)desc->qw1);
+}
+
 static int qi_check_fault(struct intel_iommu *iommu, int index, int wait_index)
 {
        u32 fault;
@@ -1216,6 +1273,8 @@ static int qi_check_fault(struct intel_iommu *iommu, int index, int wait_index)
                return -EAGAIN;
 
        fault = readl(iommu->reg + DMAR_FSTS_REG);
+       if (fault & (DMA_FSTS_IQE | DMA_FSTS_ITE | DMA_FSTS_ICE))
+               qi_dump_fault(iommu, fault);
 
        /*
         * If IQE happens, the head points to the descriptor associated
@@ -1232,12 +1291,10 @@ static int qi_check_fault(struct intel_iommu *iommu, int index, int wait_index)
                         * used by software as private data. We won't print
                         * out these two qw's for security consideration.
                         */
-                       pr_err("VT-d detected invalid descriptor: qw0 = %llx, qw1 = %llx\n",
-                              (unsigned long long)desc->qw0,
-                              (unsigned long long)desc->qw1);
                        memcpy(desc, qi->desc + (wait_index << shift),
                               1 << shift);
                        writel(DMA_FSTS_IQE, iommu->reg + DMAR_FSTS_REG);
+                       pr_info("Invalidation Queue Error (IQE) cleared\n");
                        return -EINVAL;
                }
        }
@@ -1254,6 +1311,7 @@ static int qi_check_fault(struct intel_iommu *iommu, int index, int wait_index)
                tail = ((tail >> shift) - 1 + QI_LENGTH) % QI_LENGTH;
 
                writel(DMA_FSTS_ITE, iommu->reg + DMAR_FSTS_REG);
+               pr_info("Invalidation Time-out Error (ITE) cleared\n");
 
                do {
                        if (qi->desc_status[head] == QI_IN_USE)
@@ -1265,8 +1323,10 @@ static int qi_check_fault(struct intel_iommu *iommu, int index, int wait_index)
                        return -EAGAIN;
        }
 
-       if (fault & DMA_FSTS_ICE)
+       if (fault & DMA_FSTS_ICE) {
                writel(DMA_FSTS_ICE, iommu->reg + DMAR_FSTS_REG);
+               pr_info("Invalidation Completion Error (ICE) cleared\n");
+       }
 
        return 0;
 }
index 1bc46b8..1732298 100644 (file)
@@ -20,6 +20,7 @@
 #include <linux/io-64-nonatomic-lo-hi.h>
 #include <linux/dmar.h>
 #include <linux/ioasid.h>
+#include <linux/bitfield.h>
 
 #include <asm/cacheflush.h>
 #include <asm/iommu.h>
@@ -80,6 +81,7 @@
 #define DMAR_IQ_SHIFT  4       /* Invalidation queue head/tail shift */
 #define DMAR_IQA_REG   0x90    /* Invalidation queue addr register */
 #define DMAR_ICS_REG   0x9c    /* Invalidation complete status register */
+#define DMAR_IQER_REG  0xb0    /* Invalidation queue error record register */
 #define DMAR_IRTA_REG  0xb8    /* Interrupt remapping table addr register */
 #define DMAR_PQH_REG   0xc0    /* Page request queue head register */
 #define DMAR_PQT_REG   0xc8    /* Page request queue tail register */
 #define DMAR_VCMD_REG          0xe10 /* Virtual command register */
 #define DMAR_VCRSP_REG         0xe20 /* Virtual command response register */
 
+#define DMAR_IQER_REG_IQEI(reg)                FIELD_GET(GENMASK_ULL(3, 0), reg)
+#define DMAR_IQER_REG_ITESID(reg)      FIELD_GET(GENMASK_ULL(47, 32), reg)
+#define DMAR_IQER_REG_ICESID(reg)      FIELD_GET(GENMASK_ULL(63, 48), reg)
+
 #define OFFSET_STRIDE          (9)
 
 #define dmar_readq(a) readq(a)