smmu 使能前后设备如何从swiotlb切换到smmu

在smmu使能之前系统中的device都是用的swiotlb。那smmu使能之后设备如何切换到smmu呢 这个时候会分两种情况
第一种是如果设备在smmu使能之前已经开始使用dma了,那smmu只能时就要通知设备从swiotlb切换到smmu。
static int __init __iommu_dma_init(void)
{
    int ret;

    ret = iommu_dma_init();
    if (!ret)
        ret = register_iommu_dma_ops_notifier(&platform_bus_type);
    if (!ret)
        ret = register_iommu_dma_ops_notifier(&amba_bustype);
#ifdef CONFIG_PCI
    if (!ret)
        ret = register_iommu_dma_ops_notifier(&pci_bus_type);
#endif
    return ret;
}
arch_initcall(__iommu_dma_init);
 可见也是只管三个总线的设备platform_bus_type/amba_bustype/pci_bus_type
其中iommu_dma_init 只是通过kmem_cache_create申请一个cache而已
static int __init register_iommu_dma_ops_notifier(struct bus_type *bus)
{
    struct notifier_block *nb = kzalloc(sizeof(*nb), GFP_KERNEL);
    int ret;

    if (!nb)
        return -ENOMEM;

    nb->notifier_call = __iommu_attach_notifier;

    ret = bus_register_notifier(bus, nb);
    if (ret) {
        pr_warn("Failed to register DMA domain notifier; IOMMU DMA ops unavailable on bus '%s'\n",
            bus->name);
        kfree(nb);
    }
    return ret;
}
register_iommu_dma_ops_notifier 中申请一个notifier_block *nb,并将callback函数设定为__iommu_attach_notifier,然后通过bus_register_notifier 来遍历platform_bus_type/amba_bustype/pci_bus_type上的每个设备,分别调用__iommu_attach_notifier。
int bus_register_notifier(struct bus_type *bus, struct notifier_block *nb)
{
    return blocking_notifier_chain_register(&bus->p->bus_notifier, nb);
}
从bus_type->p->bus_notifier 可以得到blocking_notifier_head *nh,也就是每个bus 中的所有的notifier 是组成一个链表的。
int blocking_notifier_chain_register(struct blocking_notifier_head *nh,
        struct notifier_block *n)
{
    int ret;

    /*
     * This code gets used during boot-up, when task switching is
     * not yet working and interrupts must remain disabled.  At
     * such times we must not call down_write().
     */
    if (unlikely(system_state == SYSTEM_BOOTING))
        return notifier_chain_register(&nh->head, n);

    down_write(&nh->rwsem);
    ret = notifier_chain_register(&nh->head, n);
    up_write(&nh->rwsem);
    return ret;
}

在blocking_notifier_chain_register中要判断一下当前系统的状态是不是SYSTEM_BOOTING,一般这种情况不满足,然后调用notifier_chain_register,从bus->p->bus_notifier 中得到notifier header的nh->head
static int notifier_chain_register(struct notifier_block **nl,
        struct notifier_block *n)
{
    while ((*nl) != NULL) {
        if (n->priority > (*nl)->priority)
            break;
        nl = &((*nl)->next);
    }
    n->next = *nl;
    rcu_assign_pointer(*nl, n);
    return 0;
}
最终还在while循环中判断当前待调用notifier的优先级是否最高,不是的话,遍历链表将当前notifier 插入list中,最后通过rcu_assign_pointer 修改指针.

第二种情况是smmu已经使能后,设备如何切换到smmu上
这种情况主要在arm_smmu_device_probe 中处理
#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
#ifdef CONFIG_ARM_AMBA
    if (amba_bustype.iommu_ops != &arm_smmu_ops) {
        ret = bus_set_iommu(&amba_bustype, &arm_smmu_ops);
        if (ret)
            return ret;
    }
#endif
    if (platform_bus_type.iommu_ops != &arm_smmu_ops) {
        ret = bus_set_iommu(&platform_bus_type, &arm_smmu_ops);
        if (ret)
            return ret;
    }
同样是这三个总线
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->iommu_ops 不是null,就说明已经调用过这个函数了,不用重复调用,一般情况下系统有多个smmmu arm_smmu_device_probe 会被调用多次,但是bus_set_iommu 只能被调用一次.
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;

out_err:
    /* Clean up */
    bus_for_each_dev(bus, NULL, &cb, remove_iommu_group);
    bus_unregister_notifier(bus, nb);

out_free:
    kfree(nb);

    return err;
}
这个函数主要做了两件事
第一个是在smmu使能之后的设备使用smmu注册notifier bus_register_notifier
第二个是为当前已有的设备调用add_iommu_group
int bus_for_each_dev(struct bus_type *bus, struct device *start,
             void *data, int (*fn)(struct device *, void *))
{
    struct klist_iter i;
    struct device *dev;
    int error = 0;

    if (!bus || !bus->p)
        return -EINVAL;

    klist_iter_init_node(&bus->p->klist_devices, &i,
                 (start ? &start->p->knode_bus : NULL));
    while ((dev = next_device(&i)) && !error)
        error = fn(dev, data);
    klist_iter_exit(&i);
    return error;
}
而add_iommu_group->add_device(arm_smmu_add_device)->device_group->iommu_group_add_device 等整个flow.

这样当device 初始化的时候会调用iommu_bus_notifier。从iommu_bus_notifier 中也可以看出一个device要使用smmu必须先add_device,然后得到group。而且每个group 都包含struct blocking_notifier_head *nh
  • 1
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值