SMMUv3中断处理过程分析(十三)
1.概述
SMMUv3驱动初始化的时候,会注册事件队列中断处理函数、错误中断处理函数和PRIQ中断处理函数。下面分析这些中断处理函数。
2.事件中断
事件中断的处理函数为arm_smmu_evtq_thread
。当事件中断发生后,中断处理函数会遍历事件队列,处理每一个事件。处理过程如下:
- 从事件队列中取出事件
- 从事件中解析Event number,只处理
EVT_ID_TRANSLATION_FAULT
、EVT_ID_ADDR_SIZE_FAULT
、EVT_ID_ACCESS_FAULT
和EVT_ID_PERMISSION_FAULT
四种类型的事件,其他事件返回-EOPNOTSUPP
。 - 判断事件中的Stall位是否设置,没有设置直接返回
-EOPNOTSUPP
,若设置,则继续处理。SMMUv3触发上述四种事件后,必须处于Stall状态,才能让CPU处理IO Page Fault。 - 根据触发事件的事务读写属性、特权以及访问类型(指令访问还是数据访问),构造IO Page Fault请求。
- 调用
iommu_report_device_fault
函数上报IO Page Fault事件。
[drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.c]
static irqreturn_t arm_smmu_evtq_thread(int irq, void *dev)
{......do {while (!queue_remove_raw(q, evt)) { // 循环读取事件u8 id = FIELD_GET(EVTQ_0_ID, evt[0]);ret = arm_smmu_handle_evt(smmu, evt); // 处理事件if (!ret || !__ratelimit(&rs))continue;......// 不能一直占用CPU处理事件,处理一段事件需要产看是否有调度请求cond_resched();}......} while (!queue_empty(llq));/* Sync our overflow flag, as we believe we're up to speed */queue_sync_cons_ovf(q);return IRQ_HANDLED;
}static int arm_smmu_handle_evt(struct arm_smmu_device *smmu, u64 *evt)
{......// 解析Event numberswitch (FIELD_GET(EVTQ_0_ID, evt[0])) {case EVT_ID_TRANSLATION_FAULT:case EVT_ID_ADDR_SIZE_FAULT:case EVT_ID_ACCESS_FAULT:case EVT_ID_PERMISSION_FAULT:break;default:return -EOPNOTSUPP;}// SMMU是否处于stall状态if (!(evt[1] & EVTQ_1_STALL))return -EOPNOTSUPP;// 设置导致FAULT的事务是读还是写if (evt[1] & EVTQ_1_RnW)perm |= IOMMU_FAULT_PERM_READ;elseperm |= IOMMU_FAULT_PERM_WRITE;// 判断导致FAULT的事务是指令还是数据if (evt[1] & EVTQ_1_InD)perm |= IOMMU_FAULT_PERM_EXEC; // 指令// 判断导致FAULT的事务是否有特权模式if (evt[1] & EVTQ_1_PnU)perm |= IOMMU_FAULT_PERM_PRIV; // 具有特权模式// iommu_fault_type为page request faultflt->type = IOMMU_FAULT_PAGE_REQ;flt->prm = (struct iommu_fault_page_request) {/* 当前页请求是一组相关页请求中的最后一个请求,这个标志在批量* 页请求处理中非常重要。*/.flags = IOMMU_FAULT_PAGE_REQUEST_LAST_PAGE,// 设置Page Request Group Index.grpid = FIELD_GET(EVTQ_1_STAG, evt[1]),.perm = perm, // 权限.addr = FIELD_GET(EVTQ_2_ADDR, evt[2]), // 导致FAULT的访问地址};if (ssid_valid) {/* 如果是SubstreamID有效,还需要设置PASID有效标志。SMMUv3将PCIe* 设备的PASID设置为SubstreamID*/flt->prm.flags |= IOMMU_FAULT_PAGE_REQUEST_PASID_VALID;flt->prm.pasid = FIELD_GET(EVTQ_0_SSID, evt[0]); // 设置PASID}......// 上报异常ret = iommu_report_device_fault(master->dev, &fault_evt);......
}
3.错误中断
事件中断的处理函数为arm_smmu_gerror_handler
。当错误中断发生后,中断处理函数先读取SMMU_()GERROR和SMMU_GERRORN寄存器。SMMU(_)GERROR寄存器保存了错误类型,SMMU_GERRORN寄存器用于响应这些错误。
[drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.c]
static irqreturn_t arm_smmu_gerror_handler(int irq, void *dev)
{......gerror = readl_relaxed(smmu->base + ARM_SMMU_GERROR);gerrorn = readl_relaxed(smmu->base + ARM_SMMU_GERRORN);active = gerror ^ gerrorn; // 获取当前pending的错误if (!(active & GERROR_ERR_MASK))return IRQ_NONE; /* No errors pending */....../* 表示SMMU进入了Service failure mode,此时SMMU停止地址转换,* 停止执行命令,停止上报事件,响应错误并不会退出Service failure mode,* 如果退出,和具体的实现相关*/if (active & GERROR_SFM_ERR) {dev_err(smmu->dev, "device has entered Service Failure Mode!\n");arm_smmu_device_disable(smmu);}// 报告全局错误的MSI中断写入出现错误,写入错误了中断如何报上来?if (active & GERROR_MSI_GERROR_ABT_ERR)dev_warn(smmu->dev, "GERROR MSI write aborted\n");// PRIQ队列MSI中断写入错误if (active & GERROR_MSI_PRIQ_ABT_ERR)dev_warn(smmu->dev, "PRIQ MSI write aborted\n");// 事件队列MSI中断写入错误if (active & GERROR_MSI_EVTQ_ABT_ERR)dev_warn(smmu->dev, "EVTQ MSI write aborted\n");// 命令队列MSI中断写入错误if (active & GERROR_MSI_CMDQ_ABT_ERR)dev_warn(smmu->dev, "CMDQ MSI write aborted\n");// PRIQ队列写入错误if (active & GERROR_PRIQ_ABT_ERR)dev_err(smmu->dev, "PRIQ write aborted -- events may have been lost\n");// 事件队列写入错误if (active & GERROR_EVTQ_ABT_ERR)dev_err(smmu->dev, "EVTQ write aborted -- events may have been lost\n");// 如果是命令错误,则移动队列的读指针,跳过错误命令if (active & GERROR_CMDQ_ERR)arm_smmu_cmdq_skip_err(smmu);writel(gerror, smmu->base + ARM_SMMU_GERRORN); // 响应错误return IRQ_HANDLED;
}
4.PRIQ中断
PRI(Page Request Interface)是PCIe规范中定义的一种机制,允许IOMMU(或SMMU)与设备之间进行页请求和页响应的交互。PRIQ(Page Request Interface Queue)是SMMUv3中用于处理PRI消息的队列。PRI机制的主要目的是支持提前为设备分配物理内存,而不是等到IOMMU上报IO Page Fault时分配物理内存,这对高速的PCIe设备提高性能有很大的帮助。
SMMUv3的PRIQ中断处理函数为arm_smmu_priq_thread
,可以看出,SMMUv3驱动还没有实现处理PRI的功能,只是简单的回复Page Response。
[drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.c]
static irqreturn_t arm_smmu_priq_thread(int irq, void *dev)
{......do {while (!queue_remove_raw(q, evt))arm_smmu_handle_ppr(smmu, evt);......} while (!queue_empty(llq));/* Sync our overflow flag, as we believe we're up to speed */queue_sync_cons_ovf(q);return IRQ_HANDLED;
}static void arm_smmu_handle_ppr(struct arm_smmu_device *smmu, u64 *evt)
{......sid = FIELD_GET(PRIQ_0_SID, evt[0]);ssv = FIELD_GET(PRIQ_0_SSID_V, evt[0]);ssid = ssv ? FIELD_GET(PRIQ_0_SSID, evt[0]) : IOMMU_NO_PASID;last = FIELD_GET(PRIQ_0_PRG_LAST, evt[0]);grpid = FIELD_GET(PRIQ_1_PRG_IDX, evt[1]);......if (last) {struct arm_smmu_cmdq_ent cmd = {.opcode = CMDQ_OP_PRI_RESP,.substream_valid = ssv,.pri = {.sid = sid,.ssid = ssid,.grpid = grpid,.resp = PRI_RESP_DENY,},};arm_smmu_cmdq_issue_cmd(smmu, &cmd);}
}
参考资料
- linux6.12 source code.
- Arm ® System Memory Management Unit Architecture Specification version 3.