Skip to content

Commit

Permalink
VT-d: handle Invalidation Queue Error to avoid system hang
Browse files Browse the repository at this point in the history
When hardware detects any error with a descriptor from the invalidation
queue, it stops fetching new descriptors from the queue until software
clears the Invalidation Queue Error bit in the Fault Status register.
Following fix handles the IQE so the kernel won't be trapped in an
infinite loop.

Signed-off-by: Yu Zhao <yu.zhao@intel.com>
Signed-off-by: David Woodhouse <David.Woodhouse@intel.com>
  • Loading branch information
Yu Zhao authored and David Woodhouse committed Feb 9, 2009
1 parent 43f7392 commit 704126a
Show file tree
Hide file tree
Showing 3 changed files with 59 additions and 26 deletions.
61 changes: 45 additions & 16 deletions drivers/pci/dmar.c
Original file line number Diff line number Diff line change
Expand Up @@ -573,19 +573,49 @@ static inline void reclaim_free_desc(struct q_inval *qi)
}
}

static int qi_check_fault(struct intel_iommu *iommu, int index)
{
u32 fault;
int head;
struct q_inval *qi = iommu->qi;
int wait_index = (index + 1) % QI_LENGTH;

fault = readl(iommu->reg + DMAR_FSTS_REG);

/*
* If IQE happens, the head points to the descriptor associated
* with the error. No new descriptors are fetched until the IQE
* is cleared.
*/
if (fault & DMA_FSTS_IQE) {
head = readl(iommu->reg + DMAR_IQH_REG);
if ((head >> 4) == index) {
memcpy(&qi->desc[index], &qi->desc[wait_index],
sizeof(struct qi_desc));
__iommu_flush_cache(iommu, &qi->desc[index],
sizeof(struct qi_desc));
writel(DMA_FSTS_IQE, iommu->reg + DMAR_FSTS_REG);
return -EINVAL;
}
}

return 0;
}

/*
* Submit the queued invalidation descriptor to the remapping
* hardware unit and wait for its completion.
*/
void qi_submit_sync(struct qi_desc *desc, struct intel_iommu *iommu)
int qi_submit_sync(struct qi_desc *desc, struct intel_iommu *iommu)
{
int rc = 0;
struct q_inval *qi = iommu->qi;
struct qi_desc *hw, wait_desc;
int wait_index, index;
unsigned long flags;

if (!qi)
return;
return 0;

hw = qi->desc;

Expand All @@ -603,7 +633,8 @@ void qi_submit_sync(struct qi_desc *desc, struct intel_iommu *iommu)

hw[index] = *desc;

wait_desc.low = QI_IWD_STATUS_DATA(2) | QI_IWD_STATUS_WRITE | QI_IWD_TYPE;
wait_desc.low = QI_IWD_STATUS_DATA(QI_DONE) |
QI_IWD_STATUS_WRITE | QI_IWD_TYPE;
wait_desc.high = virt_to_phys(&qi->desc_status[wait_index]);

hw[wait_index] = wait_desc;
Expand All @@ -614,13 +645,11 @@ void qi_submit_sync(struct qi_desc *desc, struct intel_iommu *iommu)
qi->free_head = (qi->free_head + 2) % QI_LENGTH;
qi->free_cnt -= 2;

spin_lock(&iommu->register_lock);
/*
* update the HW tail register indicating the presence of
* new descriptors.
*/
writel(qi->free_head << 4, iommu->reg + DMAR_IQT_REG);
spin_unlock(&iommu->register_lock);

while (qi->desc_status[wait_index] != QI_DONE) {
/*
Expand All @@ -630,15 +659,21 @@ void qi_submit_sync(struct qi_desc *desc, struct intel_iommu *iommu)
* a deadlock where the interrupt context can wait indefinitely
* for free slots in the queue.
*/
rc = qi_check_fault(iommu, index);
if (rc)
goto out;

spin_unlock(&qi->q_lock);
cpu_relax();
spin_lock(&qi->q_lock);
}

qi->desc_status[index] = QI_DONE;
out:
qi->desc_status[index] = qi->desc_status[wait_index] = QI_DONE;

reclaim_free_desc(qi);
spin_unlock_irqrestore(&qi->q_lock, flags);

return rc;
}

/*
Expand All @@ -651,13 +686,13 @@ void qi_global_iec(struct intel_iommu *iommu)
desc.low = QI_IEC_TYPE;
desc.high = 0;

/* should never fail */
qi_submit_sync(&desc, iommu);
}

int qi_flush_context(struct intel_iommu *iommu, u16 did, u16 sid, u8 fm,
u64 type, int non_present_entry_flush)
{

struct qi_desc desc;

if (non_present_entry_flush) {
Expand All @@ -671,10 +706,7 @@ int qi_flush_context(struct intel_iommu *iommu, u16 did, u16 sid, u8 fm,
| QI_CC_GRAN(type) | QI_CC_TYPE;
desc.high = 0;

qi_submit_sync(&desc, iommu);

return 0;

return qi_submit_sync(&desc, iommu);
}

int qi_flush_iotlb(struct intel_iommu *iommu, u16 did, u64 addr,
Expand Down Expand Up @@ -704,10 +736,7 @@ int qi_flush_iotlb(struct intel_iommu *iommu, u16 did, u64 addr,
desc.high = QI_IOTLB_ADDR(addr) | QI_IOTLB_IH(ih)
| QI_IOTLB_AM(size_order);

qi_submit_sync(&desc, iommu);

return 0;

return qi_submit_sync(&desc, iommu);
}

/*
Expand Down
21 changes: 12 additions & 9 deletions drivers/pci/intr_remapping.c
Original file line number Diff line number Diff line change
Expand Up @@ -207,15 +207,15 @@ int alloc_irte(struct intel_iommu *iommu, int irq, u16 count)
return index;
}

static void qi_flush_iec(struct intel_iommu *iommu, int index, int mask)
static int qi_flush_iec(struct intel_iommu *iommu, int index, int mask)
{
struct qi_desc desc;

desc.low = QI_IEC_IIDEX(index) | QI_IEC_TYPE | QI_IEC_IM(mask)
| QI_IEC_SELECTIVE;
desc.high = 0;

qi_submit_sync(&desc, iommu);
return qi_submit_sync(&desc, iommu);
}

int map_irq_to_irte_handle(int irq, u16 *sub_handle)
Expand Down Expand Up @@ -283,6 +283,7 @@ int clear_irte_irq(int irq, struct intel_iommu *iommu, u16 index)

int modify_irte(int irq, struct irte *irte_modified)
{
int rc;
int index;
struct irte *irte;
struct intel_iommu *iommu;
Expand All @@ -303,14 +304,15 @@ int modify_irte(int irq, struct irte *irte_modified)
set_64bit((unsigned long *)irte, irte_modified->low | (1 << 1));
__iommu_flush_cache(iommu, irte, sizeof(*irte));

qi_flush_iec(iommu, index, 0);

rc = qi_flush_iec(iommu, index, 0);
spin_unlock(&irq_2_ir_lock);
return 0;

return rc;
}

int flush_irte(int irq)
{
int rc;
int index;
struct intel_iommu *iommu;
struct irq_2_iommu *irq_iommu;
Expand All @@ -326,10 +328,10 @@ int flush_irte(int irq)

index = irq_iommu->irte_index + irq_iommu->sub_handle;

qi_flush_iec(iommu, index, irq_iommu->irte_mask);
rc = qi_flush_iec(iommu, index, irq_iommu->irte_mask);
spin_unlock(&irq_2_ir_lock);

return 0;
return rc;
}

struct intel_iommu *map_ioapic_to_ir(int apic)
Expand All @@ -355,6 +357,7 @@ struct intel_iommu *map_dev_to_ir(struct pci_dev *dev)

int free_irte(int irq)
{
int rc = 0;
int index, i;
struct irte *irte;
struct intel_iommu *iommu;
Expand All @@ -375,7 +378,7 @@ int free_irte(int irq)
if (!irq_iommu->sub_handle) {
for (i = 0; i < (1 << irq_iommu->irte_mask); i++)
set_64bit((unsigned long *)irte, 0);
qi_flush_iec(iommu, index, irq_iommu->irte_mask);
rc = qi_flush_iec(iommu, index, irq_iommu->irte_mask);
}

irq_iommu->iommu = NULL;
Expand All @@ -385,7 +388,7 @@ int free_irte(int irq)

spin_unlock(&irq_2_ir_lock);

return 0;
return rc;
}

static void iommu_set_intr_remapping(struct intel_iommu *iommu, int mode)
Expand Down
3 changes: 2 additions & 1 deletion include/linux/intel-iommu.h
Original file line number Diff line number Diff line change
Expand Up @@ -194,6 +194,7 @@ static inline void dmar_writeq(void __iomem *addr, u64 val)
/* FSTS_REG */
#define DMA_FSTS_PPF ((u32)2)
#define DMA_FSTS_PFO ((u32)1)
#define DMA_FSTS_IQE (1 << 4)
#define dma_fsts_fault_record_index(s) (((s) >> 8) & 0xff)

/* FRCD_REG, 32 bits access */
Expand Down Expand Up @@ -328,7 +329,7 @@ extern int qi_flush_iotlb(struct intel_iommu *iommu, u16 did, u64 addr,
unsigned int size_order, u64 type,
int non_present_entry_flush);

extern void qi_submit_sync(struct qi_desc *desc, struct intel_iommu *iommu);
extern int qi_submit_sync(struct qi_desc *desc, struct intel_iommu *iommu);

extern void *intel_alloc_coherent(struct device *, size_t, dma_addr_t *, gfp_t);
extern void intel_free_coherent(struct device *, size_t, void *, dma_addr_t);
Expand Down

0 comments on commit 704126a

Please sign in to comment.