Commit 6ca69e58 authored by Lu Baolu's avatar Lu Baolu Committed by Joerg Roedel
Browse files

iommu/vt-d: Report more information about invalidation errors



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: default avatarAshok Raj <ashok.raj@intel.com>
Tested-by: default avatarGuo Kaijie <Kaijie.Guo@intel.com>
Signed-off-by: default avatarLu Baolu <baolu.lu@linux.intel.com>
Link: https://lore.kernel.org/r/20210318005340.187311-1-baolu.lu@linux.intel.com


Signed-off-by: default avatarJoerg Roedel <jroedel@suse.de>
parent dec991e4
Loading
Loading
Loading
Loading
+64 −4
Original line number Diff line number Diff line
@@ -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;
}
+6 −0
Original line number Diff line number Diff line
@@ -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 */
@@ -126,6 +128,10 @@
#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)