smmu学习笔记之bus_set_iommu

本文详细解析了ARM系统中SMMU设备如何通过iommu_ops进行初始化,并加入到相应的IOMMU组中。重点介绍了bus_set_iommu函数如何设置IOMMU操作,iommu_bus_init如何注册总线类型上的设备通知,以及add_iommu_group如何为每个PCI设备创建IOMMU组。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

在arm_smmu_device_probe 中会针对pci bus设定iommu_ops
#ifdef CONFIG_PCI
    if (pci_bus_type.iommu_ops != &arm_smmu_ops) {
        pci_request_acs();
        ret = bus_set_iommu(&pci_bus_type, &arm_smmu_ops);
        if (ret)
            return ret;
    }
#endif
这个if条件显然成立
int bus_set_iommu(struct bus_type *bus, const struct iommu_ops *ops)
{
    int err;

    if (bus->iommu_ops != NULL)
        return -EBUSY;

    bus->iommu_ops = ops;

    /* Do IOMMU specific setup for this bus-type */
    err = iommu_bus_init(bus, ops);
    if (err)
        bus->iommu_ops = NULL;

    return err;
}
bus_set_iommu 中将arm_smmu_ops 赋值给bus->iommu_ops,然后调用iommu_bus_init 为bus上的每个device添加group
static int iommu_bus_init(struct bus_type *bus, const struct iommu_ops *ops)
{
    int err;
    struct notifier_block *nb;
    struct iommu_callback_data cb = {
        .ops = ops,
    };

    nb = kzalloc(sizeof(struct notifier_block), GFP_KERNEL);
    if (!nb)
        return -ENOMEM;

    nb->notifier_call = iommu_bus_notifier;

    err = bus_register_notifier(bus, nb);
    if (err)
        goto out_free;

    err = bus_for_each_dev(bus, NULL, &cb, add_iommu_group);
    if (err)
        goto out_err;


    return 0;

}
iommu_bus_init中会通过bus_for_each_dev遍历pci上的device,会为每个device调用add_iommu_group
static int add_iommu_group(struct device *dev, void *data)
{
    struct iommu_callback_data *cb = data;
    const struct iommu_ops *ops = cb->ops;
    int ret;

    if (!ops->add_device)
        return 0;

    WARN_ON(dev->iommu_group);

    ret = ops->add_device(dev);
}
add_iommu_group 只是一个wrapper函数最终还是调用ops->add_device,前面我们讲了ops就等于arm_smmu_ops,因此调用
arm_smmu_add_device->iommu_group_get_for_dev
struct iommu_group *iommu_group_get_for_dev(struct device *dev)
{
    const struct iommu_ops *ops = dev->bus->iommu_ops;
    struct iommu_group *group;
    int ret;

    group = iommu_group_get(dev);
    if (group)
        return group;

    group = ERR_PTR(-EINVAL);

    if (ops && ops->device_group)
        group = ops->device_group(dev);

    if (IS_ERR(group))
        return group;

    /*
     * Try to allocate a default domain - needs support from the
     * IOMMU driver.
     */
    if (!group->default_domain) {
        group->default_domain = __iommu_domain_alloc(dev->bus,
                                 IOMMU_DOMAIN_DMA);
        if (!group->domain)
            group->domain = group->default_domain;
    }

    ret = iommu_group_add_device(group, dev);
    if (ret) {
        iommu_group_put(group);
        return ERR_PTR(ret);
    }

    return group;
}
在iommu_group_get_for_dev 中首先通过iommu_group_get得到group,这个时候group肯定是null的,但是ops->device_group 不为null,因此通过group = ops->device_group(dev);赋值,这里的device_group就是arm_smmu_device_group
static struct iommu_group *arm_smmu_device_group(struct device *dev)
{
    struct iommu_group *group;

    /*
     * We don't support devices sharing stream IDs other than PCI RID
     * aliases, since the necessary ID-to-device lookup becomes rather
     * impractical given a potential sparse 32-bit stream ID space.
     */
    if (dev_is_pci(dev))
        group = pci_device_group(dev);
    else
        group = generic_device_group(dev);

    return group;
}
由于我们是pci因此group就是pci_device_group。
回到iommu_group_get_for_dev 中继续调用iommu_group_add_device
int iommu_group_add_device(struct iommu_group *group, struct device *dev)
{
    int ret, i = 0;
    struct iommu_device *device;

    device = kzalloc(sizeof(*device), GFP_KERNEL);
    if (!device)
        return -ENOMEM;

    device->dev = dev;

    ret = sysfs_create_link(&dev->kobj, &group->kobj, "iommu_group");
###
创建iommu_group的软连接
root@ubuntu:/sys/kernel/iommu_groups/0/devices/0002:80:00.0# ll
total 0
drwxr-xr-x 4 root root    0 Mar  7 16:01 ./
drwxr-xr-x 6 root root    0 Mar  7 16:01 ../
-rw-r--r-- 1 root root 4096 Mar  7 16:01 broken_parity_status
-r--r--r-- 1 root root 4096 Mar  7 16:01 class
-rw-r--r-- 1 root root 4096 Mar  7 16:01 config
-r--r--r-- 1 root root 4096 Mar  7 16:01 consistent_dma_mask_bits
-rw-r--r-- 1 root root 4096 Mar  7 16:01 d3cold_allowed
-r--r--r-- 1 root root 4096 Mar  7 16:01 device
-r--r--r-- 1 root root 4096 Mar  7 16:01 devspec
-r--r--r-- 1 root root 4096 Mar  7 16:01 dma_mask_bits
lrwxrwxrwx 1 root root    0 Mar  7 16:01 driver -> ../../../bus/pci/drivers/pcieport/
-rw-r--r-- 1 root root 4096 Mar  7 16:01 driver_override
-rw-r--r-- 1 root root 4096 Mar  7 16:01 enable
lrwxrwxrwx 1 root root    0 Mar  7 16:01 iommu_group -> ../../../kernel/iommu_groups/0/

###
    device->name = kasprintf(GFP_KERNEL, "%s", kobject_name(&dev->kobj));
rename:
    if (!device->name) {
        sysfs_remove_link(&dev->kobj, "iommu_group");
        kfree(device);
        return -ENOMEM;
    }

    ret = sysfs_create_link_nowarn(group->devices_kobj,
                       &dev->kobj, device->name);
##
创建软连接
root@ubuntu:/sys/kernel/iommu_groups/0/devices# ll
total 0
drwxr-xr-x 2 root root 0 Mar  7 16:41 ./
drwxr-xr-x 3 root root 0 Mar  7 16:41 ../
lrwxrwxrwx 1 root root 0 Mar  7 16:41 0002:80:00.0 -> ../../../../devices/pci0002:80/0002:80:00.0/

##

    kobject_get(group->devices_kobj);

    dev->iommu_group = group;

    iommu_group_create_direct_mappings(group, dev);

    mutex_lock(&group->mutex);
    list_add_tail(&device->list, &group->devices);
    if (group->domain)
        __iommu_attach_device(group->domain, dev);
    mutex_unlock(&group->mutex);

    /* Notify any listeners about change to group. */
    blocking_notifier_call_chain(&group->notifier,
                     IOMMU_GROUP_NOTIFY_ADD_DEVICE, dev);

    trace_add_device_to_group(group->id, dev);

    pr_info("Adding device %s to group %d\n", dev_name(dev), group->id);

    return 0;
}

这样就能在开机log中看到[    4.845579] iommu: Adding device 0002:80:00.0 to group 0  字样
通过在iommu_groups 下就能看到当前的device,可见device id是从0开始的
root@ubuntu:/sys/kernel/iommu_groups# ls
0  1


<think>我们正在讨论SMMU(System MemoryManagement Unit)中的CMD_SYNC超时问题。根据用户提供的引用资料,特别是引用[2]和引用[3],我们可以分析可能导致CMD_SYNC超时的原因。首先,回顾一下CMD_SYNC的作用:在SMMU的命令队列(command queue)中,CMD_SYNC命令用于同步命令队列的执行。当软件向命令队列提交了一个CMD_SYNC命令后,它会等待该命令完成,以确保在此之前提交的所有命令都已经执行完毕。如果CMD_SYNC命令没有在预期时间内完成,就会发生超时。 根据引用[2]和引用[3],我们可以分析以下可能导致CMD_SYNC超时的原因: 1. **命令队列处理阻塞**:SMMU可能因为某些原因而无法处理命令队列中的命令。例如,如果SMMU正在处理一个耗时的操作(如大量的TLB无效操作),那么命令队列的处理可能会被延迟。2.**中断处理问题**:引用[2]提到,当命令队列中的CMD_SYNC命令完成时,会产生一个cmdq-sync中断。如果这个中断没有被正确触发或者被屏蔽,那么软件可能无法得知CMD_SYNC已经完成,从而导致超时。此外,如果中断控制器(如GIC)配置有问题,或者中断处理程序没有正确响应,也可能导致超时。3.**内存访问问题**:命令队列和事件队列都位于内存中。如果SMMU访问内存时遇到问题(比如内存访问延迟、内存错误、缓存一致性问题等),那么命令的处理可能会被延迟。引用[3]强调了SMMU在更新队列的生产者指针(PROD)之前,必须确保队列数据对消费者(软件)可见。如果由于缓存一致性问题导致数据不可见,可能会引起同步问题。 4. **SMMU内部错误**:引用[2]提到,当发生错误时,SMMU会通过gerror中断报告。如果SMMU内部发生错误(如配置错误、硬件故障等),可能导致命令队列停止处理,从而引起CMD_SYNC超时。此外,引用[1]提到,错误的配置可能导致SMMU不输出非连续的属性,这也可能间接影响命令队列的处理。 5. **队列满或溢出**:如果命令队列已满,新的命令(包括CMD_SYNC)无法被加入队列,这会导致软件无法提交命令,从而可能引起超时。或者,如果队列溢出,可能会导致命令丢失。6.**软件配置问题**:软件可能配置了不正确的命令队列基地址或大小,或者没有正确更新生产者指针和消费者指针,导致SMMU无法正确读取命令。7.**并发问题**:在多核系统中,如果多个处理器核心同时操作命令队列,而没有适当的同步机制,可能会导致队列状态不一致,从而引起超时。8.**硬件性能问题**:如果SMMU的时钟频率过低,或者系统总线拥堵,可能导致命令处理延迟。 针对这些可能的原因,我们可以采取以下步骤进行排查: -检查SMMU的中断状态,特别是gerror中断和cmdq-sync中断,看是否有错误发生或中断未触发。-检查命令队列的内存区域是否配置正确,并且软件正确更新了生产者指针(确保SMMU能看到新的命令)。 -检查缓存一致性设置,确保SMMU访问的内存区域是cache coherent的(如引用[2]中提到的dma-coherent属性)。 -检查命令队列是否已满,可以通过比较生产者指针和消费者指针来判断。-检查SMMU的配置,确保没有错误的配置(如引用[1]中提到的属性配置问题)。 -在软件层面,确保在提交CMD_SYNC命令后,正确等待中断或轮询状态寄存器,以检测命令完成。 此外,引用[3]中关于队列同步的说明也很重要:生产者(软件)在更新PROD指针之前,必须确保所有队列项已经写入内存,并且对于SMMU输出队列,SMMU在更新PROD之前必须确保数据在内存共享域中可见。因此,软件在更新命令队列的生产者指针时,需要使用适当的内存屏障指令,以确保SMMU能看到新的命令。综上所述,CMD_SYNC超时的原因可能涉及硬件、软件、配置和中断等多个方面,需要综合排查。</think>### SMMU CMD_SYNC超时可能原因分析 在SMMU架构中,`CMD_SYNC`命令用于确保命令队列中的所有前置命令执行完毕。超时通常由以下原因导致: #### 1. **命令队列阻塞** - **队列满或溢出**:当命令队列(Command Queue)被填满时,新命令无法加入,导致`CMD_SYNC`无法被处理[^3]。 - **生产者-消费者同步失败**:SMMU要求生产者在更新生产者指针(PROD)前,必须确保队列数据对消费者可见。若缓存一致性未满足(如未配置`dma-coherent`),SMMU可能无法看到新命令[^2][^3]。 #### 2. **中断配置问题** - **中断未触发**:`CMD_SYNC`完成依赖`cmdq-sync`中断通知。若中断控制器(MSI-parent)配置错误、中断被屏蔽或未注册处理程序,软件无法感知命令完成[^2]。 - **组合中断冲突**:若使用`combined`组合中断,但硬件不支持,可能导致中断丢失[^2]。 #### 3. **内存访问异常** - **缓存一致性问题**:SMMU访问非缓存一致内存(Non-cacheable)时,若软件未正确刷新缓存,SMMU可能读取到旧队列数据[^1][^2]。 - **内存属性配置错误**:如`Outer Shareablity`属性未按规范配置,导致SMMU无法正确处理队列[^1]。 #### 4. **SMMU内部错误** - **硬件故障**:`gerror`中断报告的错误(如队列访问超时)会阻塞命令处理[^2]。 - **TLB锁定/竞争**:大规模TLB无效命令(如`CMD_TLBI`)执行耗时过长,阻塞后续命令。 #### 5. **软件配置缺陷** - **流表配置错误**:StreamID映射异常导致SMMU忽略设备请求。 - **队列指针未更新**:软件未正确更新消费者指针(CONS),SMMU认为队列无新命令[^3]。 #### 6. **并发冲突** - **多核竞争**:多个CPU核心同时操作命令队列时,缺乏同步机制导致状态不一致。 - **优先级反转**:高优先级任务长时间占用SMMU资源。 --- ### 排查建议 1. **检查中断状态**:确认`cmdq-sync`中断是否触发,检查`gerror`寄存器记录的错误码[^2]。 2. **验证内存属性**:确保命令队列内存区域配置为`Cacheable + Shareable`[^1][^2]。 3. **监控队列指针**:对比PROD/CONS指针值,判断队列是否停滞[^3]。 4. **简化测试用例**:发送单条`CMD_SYNC`命令,排除前置命令干扰。 ```c // 示例:检测gerror中断状态 if (readl(SMMU_GERROR) & GERROR_CMDQ_ERR) { // 处理命令队列错误 } ```
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值