Linux kernel IOMMU代码分析笔记(4)——fault处理函数

dmar_fault是发生DMA remapping faultinterrupt remapping fault时的处理函数。代码如下:

irqreturn_t dmar_fault(int irq, void *dev_id)
{
    struct intel_iommu *iommu = dev_id;
    int reg, fault_index;
    u32 fault_status;
    unsigned long flag;

    raw_spin_lock_irqsave(&iommu->register_lock, flag);
    fault_status = readl(iommu->reg + DMAR_FSTS_REG);
    if (fault_status)
        pr_err("DRHD: handling fault status reg %x\n", fault_status);

    /* TBD: ignore advanced fault log currently */
    if (!(fault_status & DMA_FSTS_PPF))
        goto unlock_exit;

    fault_index = dma_fsts_fault_record_index(fault_status);
    reg = cap_fault_reg_offset(iommu->cap);
    while (1) {
        u8 fault_reason;
        u16 source_id;
        u64 guest_addr;
        int type;
        u32 data;

        /* highest 32 bits */
        data = readl(iommu->reg + reg +
                fault_index * PRIMARY_FAULT_REG_LEN + 12);
        if (!(data & DMA_FRCD_F))
            break;

        fault_reason = dma_frcd_fault_reason(data);
        type = dma_frcd_type(data);

        data = readl(iommu->reg + reg +
                fault_index * PRIMARY_FAULT_REG_LEN + 8);
        source_id = dma_frcd_source_id(data);

        guest_addr = dmar_readq(iommu->reg + reg +
                fault_index * PRIMARY_FAULT_REG_LEN);
        guest_addr = dma_frcd_page_addr(guest_addr);
        /* clear the fault */
        writel(DMA_FRCD_F, iommu->reg + reg +
            fault_index * PRIMARY_FAULT_REG_LEN + 12);

        raw_spin_unlock_irqrestore(&iommu->register_lock, flag);

        dmar_fault_do_one(iommu, type, fault_reason,
                source_id, guest_addr);

        fault_index++;
        if (fault_index >= cap_num_fault_regs(iommu->cap))
            fault_index = 0;
        raw_spin_lock_irqsave(&iommu->register_lock, flag);
    }

    writel(DMA_FSTS_PFO | DMA_FSTS_PPF, iommu->reg + DMAR_FSTS_REG);

unlock_exit:
    raw_spin_unlock_irqrestore(&iommu->register_lock, flag);
    return IRQ_HANDLED;
}  

分析一下这段代码:

(1)

fault_status = readl(iommu->reg + DMAR_FSTS_REG);
if (fault_status)
    pr_err("DRHD: handling fault status reg %x\n", fault_status);

/* TBD: ignore advanced fault log currently */
if (!(fault_status & DMA_FSTS_PPF))
    goto unlock_exit;

fault_index = dma_fsts_fault_record_index(fault_status);

DMAR_FSTS_REGFault Status Register,其中PPFPrimary Pending Fault)是否置位表明fault recording registers是否还有fault信息。而fault_index则是第一个含有fault信息的fault recording registers的索引。

(2)

reg = cap_fault_reg_offset(iommu->cap);

计算fault recording registers的地址偏移量。

(3)接下来的while循环会读取包含fault信息的fault recording registers

(4)

    data = readl(iommu->reg + reg +
            fault_index * PRIMARY_FAULT_REG_LEN + 12);
    if (!(data & DMA_FRCD_F))
        break;

    fault_reason = dma_frcd_fault_reason(data);
    type = dma_frcd_type(data);

    data = readl(iommu->reg + reg +
            fault_index * PRIMARY_FAULT_REG_LEN + 8);
    source_id = dma_frcd_source_id(data);

    guest_addr = dmar_readq(iommu->reg + reg +
            fault_index * PRIMARY_FAULT_REG_LEN);
    guest_addr = dma_frcd_page_addr(guest_addr);
    /* clear the fault */
    writel(DMA_FRCD_F, iommu->reg + reg +
        fault_index * PRIMARY_FAULT_REG_LEN + 12);

fault recording registers读取fault信息。需要注意的是,由于X86平台是小端模式,所以寄存器的高位内容会位于内存的高地址空间。另外,每读取完一个fault recording register信息,要把DMA_FRCD_F写回寄存器,用来表明软件已经读完了。

(5)dmar_fault_do_one是格式化打印的fault信息,其代码如下:

static int dmar_fault_do_one(struct intel_iommu *iommu, int type,
        u8 fault_reason, u16 source_id, unsigned long long addr)
{
    const char *reason;
    int fault_type;

    reason = dmar_get_fault_reason(fault_reason, &fault_type);

    if (fault_type == INTR_REMAP)
        pr_err("INTR-REMAP: Request device [[%02x:%02x.%d] "
               "fault index %llx\n"
            "INTR-REMAP:[fault reason %02d] %s\n",
            (source_id >> 8), PCI_SLOT(source_id & 0xFF),
            PCI_FUNC(source_id & 0xFF), addr >> 48,
            fault_reason, reason);
    else
        pr_err("DMAR:[%s] Request device [%02x:%02x.%d] "
               "fault addr %llx \n"
               "DMAR:[fault reason %02d] %s\n",
               (type ? "DMA Read" : "DMA Write"),
               (source_id >> 8), PCI_SLOT(source_id & 0xFF),
               PCI_FUNC(source_id & 0xFF), addr, fault_reason, reason);
    return 0;
}

(6)

writel(DMA_FSTS_PFO | DMA_FSTS_PPF, iommu->reg + DMAR_FSTS_REG);

最后表明软件已处理完所有的fault信息。