• iommu分析之DMA remap框架实现


    本文主要介绍iommu的框架。基于4.19.204内核

    IOMMU核心框架是管理IOMMU设备的一个通过框架,IOMMU设备通过实现特定的回调函数并将自身注册到IOMMU核心框架中,以此通过IOMMU核心框架提供的API向整个内核提供IOMMU功能。

    1、借用互联网的图:

    该图几乎到处可见,大致表明了iommu在内核中的地位,但是需要注意的是,这个只表明了iommu的 dma 翻译功能,没有表明其 irq_remap 的功能。

    2、iommu的驱动模块抽象
    不同的arch,不同的iommu硬件,怎么去抽象对应的公共部分呢?我们知道,iommu的主要作用就是为了给设备提供可控制的dma环境,没有mmu的时候,
    直接物理地址访问,有了mmu,可以对内存地址访问做控制了,同样可以类比系统的dma和iommu。

    主要的抽象实现在:
    linux/drivers/iommu 目录下,之前iommu目录下混乱地集合了各个arch的iommu驱动,在新一点的内核中,将常见的intel、amd、arm三大架构的独立的文件夹管理开来。
    但是其他小芯片的还是散落在这个目录下。

    iommu.c:主要抽象的对象为:

    iommu_device--- iommu 设备,指的是提供iommu功能的设备,所有的IOMMU设备都嵌入了一个 struct iommu_device
    iommu_group---iommu 的组对象,多个dev 可以用同一个组,他是iommu管理的最小单元。
    iommu_domain---iommu 的domain 对象,可以关联一个 group,
    iommu_resv_region ----保留区域,不需要iommu映射的区域。

    主要的方法就是:

    iommu_device_register--- iommu 设备注册,简单挂一下管理链表
    iommu_device_unregister ---iommu 设备注销,这个注销主要就是注册的逆操作,从管理链表中摘除
    static int iommu_insert_resv_region(struct iommu_resv_region *new,
    				    struct list_head *regions)//caq:在某个位置插入,相同类型则合并,不同类型不能合并
    int iommu_get_group_resv_regions(struct iommu_group *group,
    				 struct list_head *head)//caq:获取一个iommu_group 的resv_regions
    static ssize_t iommu_group_show_resv_regions(struct iommu_group *group,
    					     char *buf)//caq:展示某个group的resv_regions
    static ssize_t iommu_group_show_type(struct iommu_group *group,
    				     char *buf)//caq:iommu_domain 类型对应的字符串
    static void iommu_group_release(struct kobject *kobj)//caq:释放一个iommu_group
    struct iommu_group *iommu_group_alloc(void)//caq:申请内存,初始化一个iommu_group,它是iommu管理的最小单元
    struct iommu_group *iommu_group_get_by_id(int id)//caq:根据id获取到iommu_group
    int iommu_group_set_name(struct iommu_group *group, const char *name)//caq:过来的一个name,设置一下,一般来说是  给group 分配一个名字,常见的就是1,2,3等,比如 /sys/kernel/iommu_groups/1,这个1就是一个iommu_group的name
    int iommu_group_add_device(struct iommu_group *group, struct device *dev)//caq:将dev加入到iommu_group
    void iommu_group_remove_device(struct device *dev)//caq:和加入group对应,从iommu_group中移除
    static int iommu_group_device_count(struct iommu_group *group)//caq:统计多少个device在此group中
    static int __iommu_group_for_each_dev(struct iommu_group *group, void *data,
    				      int (*fn)(struct device *, void *))//caq:对group中device进行迭代并执行fn
    struct iommu_group *generic_device_group(struct device *dev)//caq:申请一个iommu_group
    
    3、iommu模块,对业务驱动往上封装 dma_map_ops 这个结构,它包含一系列常用的dma 函数指针。
    比如intel硬件的iommu对业务驱动实现实例化如下:
    

    const struct dma_map_ops intel_dma_ops = {//caq:dma_map_ops 注意与 iommu_ops 区别
    .alloc = intel_alloc_coherent,//caq:创建一个一致性内存映射,要么nocache,要么soc保证cache一致性
    .free = intel_free_coherent,//caq:释放一段一致性内存映射,
    .map_sg = intel_map_sg,//caq:分散聚集io的映射
    .unmap_sg = intel_unmap_sg,
    .map_page = intel_map_page,//caq:映射page
    .unmap_page = intel_unmap_page,
    .mapping_error = intel_mapping_error,

    ifdef CONFIG_X86

    .dma_supported = dma_direct_supported,
    

    endif

    };//caq:所有的iommu硬件,对外需要提供给驱动 一个dma_map_ops 的实现,对内需要按照 iommu 的抽象,来实例化 iommu_ops

    这个dma_map_ops的实例,
    **intel 下会赋值给 dma_ops 这个全局的变量来保存,**
    **arm下 会赋值给 arm_smmu_ops 这个全局变量,**
    **同时,他们会在 iommu_device 的ops 成员中保存,以便调用。**
    比如查看对应的dma_ops,有的时候是:
    

    crash> p dma_ops->alloc
    $8 = (void ()(struct device *, size_t, dma_addr_t *, gfp_t, unsigned long)) 0xffffffffa92eefb0
    crash> dis -l 0xffffffffa92eefb0
    /build/linux-hwe-5.4-Cf7BMf/linux-hwe-5.4-5.4.0/drivers/iommu/intel-iommu.c: 3658
    0xffffffffa92eefb0 <intel_alloc_coherent>: nopl 0x0(%rax,%rax,1) [FTRACE NOP]
    0xffffffffa92eefb5 <intel_alloc_coherent+5>: push %rbp

    有的时候又是这样的,比如使用的是swiotlb作为iommu,
    

    crash> p dma_ops.alloc
    $3 = (void ()(struct device *, size_t, dma_addr_t *, gfp_t, unsigned long)) 0xffffffffacf23ab0 //caq:swiotlb_alloc
    crash> dis -l 0xffffffffacf23ab0
    /home/kernel/linux_config1/linux-4.19.40/kernel/dma/swiotlb.c: 1045
    0xffffffffacf23ab0 <swiotlb_alloc>: nopl 0x0(%rax,%rax,1) [FTRACE NOP]
    /home/kernel/linux_config1/linux-4.19.40/kernel/dma/swiotlb.c: 1049

    还有的时候是这样子的,比如arm smmuv3的:
    

    crash> p arm_smmu_ops.domain_alloc
    $2 = (struct iommu_domain ()(unsigned int)) 0xffff00001065df78 <arm_smmu_domain_alloc>

    
    4、iommu模块,对内需要实现 内核抽象的 iommu 框架,它需要实现 iommu_ops ,后续会有一篇文章描述设备驱动怎么调用 dma_map_ops  的接口(todo) :
    

    struct iommu_ops {//caq:一个iommu硬件对内必须实现的ops
    bool (*capable)(enum iommu_cap);//caq:该iommu 设备的能力,这里写为设备而不是硬件,是因为完成iommu的可能是软件,如swiotlb

    /* Domain allocation and freeing by the iommu driver */
    struct iommu_domain *(*domain_alloc)(unsigned iommu_domain_type);//caq:创建一个iommu_domain
    void (*domain_free)(struct iommu_domain *);//caq:释放一个iommu_domain
    
    int (*attach_dev)(struct iommu_domain *domain, struct device *dev);//caq:关联一个dev到一个iommu_domain,如 arm_smmu_attach_dev
    void (*detach_dev)(struct iommu_domain *domain, struct device *dev);//caq:从iommu_domain中取消一个dev关联
    int (*map)(struct iommu_domain *domain, unsigned long iova,
    	   phys_addr_t paddr, size_t size, int prot);//caq:将iova与phy的addr进行map,map后返回给驱动
    size_t (*unmap)(struct iommu_domain *domain, unsigned long iova,
    	     size_t size);
    void (*flush_iotlb_all)(struct iommu_domain *domain);//caq:flush iotlb
    void (*iotlb_range_add)(struct iommu_domain *domain,//caq:iotlb 增加一段区域
    			unsigned long iova, size_t size);
    void (*iotlb_sync)(struct iommu_domain *domain);//caq:tlb同步
    phys_addr_t (*iova_to_phys)(struct iommu_domain *domain, dma_addr_t iova);//caq:核心热点函数
    int (*add_device)(struct device *dev);//caq:add 是指add到iommu_group中
    void (*remove_device)(struct device *dev);//caq:remove是从iommu_group中移除
    struct iommu_group *(*device_group)(struct device *dev);//caq:从device获取到对应的group
    int (*domain_get_attr)(struct iommu_domain *domain,
    		       enum iommu_attr attr, void *data);//caq:获取attr属性
    int (*domain_set_attr)(struct iommu_domain *domain,
    		       enum iommu_attr attr, void *data);//caq:设置attr属性
    
    /* Request/Free a list of reserved regions for a device */
    void (*get_resv_regions)(struct device *dev, struct list_head *list);//caq:获取dev的所有resv的regions
    void (*put_resv_regions)(struct device *dev, struct list_head *list);//caq:从dev的地址空间释放一段resv的regions
    void (*apply_resv_region)(struct device *dev,
    			  struct iommu_domain *domain,
    			  struct iommu_resv_region *region);//caq:对dev地址空间中的保留空间做进一步apply
    
    /* Window handling functions */
    int (*domain_window_enable)(struct iommu_domain *domain, u32 wnd_nr,//caq:已废弃,不用管
    			    phys_addr_t paddr, u64 size, int prot);
    void (*domain_window_disable)(struct iommu_domain *domain, u32 wnd_nr);//caq:已废弃,不用管
    /* Set the number of windows per domain */
    int (*domain_set_windows)(struct iommu_domain *domain, u32 w_count);//caq:已废弃,不用管
    /* Get the number of windows per domain */
    u32 (*domain_get_windows)(struct iommu_domain *domain);//caq:已废弃,不用管
    
    int (*of_xlate)(struct device *dev, struct of_phandle_args *args);
    bool (*is_attach_deferred)(struct iommu_domain *domain, struct device *dev);
    
    unsigned long pgsize_bitmap;//caq:该iommu支持的page size 的bitmap集合
    

    };

    
    而 intel 硬件对这个的实例化为:
    

    const struct iommu_ops intel_iommu_ops = {//caq:和arm_smmu_ops 并列的一个iommu_ops实例
    .capable = intel_iommu_capable,//caq:该iommu 硬件的能力
    .domain_alloc = intel_iommu_domain_alloc,//caq:分配 dmar_domain,并返回 iommu_domain
    .domain_free = intel_iommu_domain_free,//caq:释放 dmar_domain
    .attach_dev = intel_iommu_attach_device,//caq:将一个设备attach 到一个iommu_domain
    .detach_dev = intel_iommu_detach_device,//caq:将一个设备 从一个iommu_domain 进行detach 掉
    .map = intel_iommu_map,//caq:将iova 与phy addr 进行map
    .unmap = intel_iommu_unmap,//caq:解除某段iova的map
    .iova_to_phys = intel_iommu_iova_to_phys,//caq:获取iova map的phyaddr
    .add_device = intel_iommu_add_device,//caq:将一个 设备添加到 iommu_group中
    .remove_device = intel_iommu_remove_device,//caq:将一个 设备从iommu_group中 移除
    .get_resv_regions = intel_iommu_get_resv_regions,//caq:获取 某个设备的 保存内存区域
    .put_resv_regions = intel_iommu_put_resv_regions,//caq:从某个设备 的保留内存区域摘除
    .device_group = pci_device_group,//caq:获取一个dev的iommu_group
    .pgsize_bitmap = INTEL_IOMMU_PGSIZES,//caq:固定4k
    };

    arm的 smmuv3将它实例化为:
    

    static struct iommu_ops arm_smmu_ops = {//smmu-v3实现的instance,注意和 dma_map_ops 区别
    .capable = arm_smmu_capable,//caq: 该iommu 设备的capability
    .domain_alloc = arm_smmu_domain_alloc,//caq:分配iommu_domain
    .domain_free = arm_smmu_domain_free,//caq:free 掉一个分配的iommu_domain
    .attach_dev = arm_smmu_attach_dev,//caq:将设备归属到对应的iommu_domain
    .map = arm_smmu_map,//caq:将iova 与 phy addr 进行map
    .unmap = arm_smmu_unmap,//caq:将iova 与 对应的phy addr 解除map
    .flush_iotlb_all = arm_smmu_iotlb_sync,//caq:注意:intel没有直接在iommu_ops中实现flush,但是在最新内核是参照arm的
    .iotlb_sync = arm_smmu_iotlb_sync,//caq:arm-v3实现的flush tlb的函数
    .iova_to_phys = arm_smmu_iova_to_phys,//caq:根据iommu_domain 与iova 获取映射过的phy addr
    .add_device = arm_smmu_add_device,//caq:将设备添加到iommu_group
    .remove_device = arm_smmu_remove_device,//caq:将设备从iommu_group中移除
    .device_group = arm_smmu_device_group,//caq:获取dev的归属iommu_group
    .domain_get_attr = arm_smmu_domain_get_attr,//caq:获取iommu_domain相关的属性,其实大多数是 arm_smmu_domain的
    .domain_set_attr = arm_smmu_domain_set_attr,//caq:设置,同上
    .of_xlate = arm_smmu_of_xlate,
    .get_resv_regions = arm_smmu_get_resv_regions,//caq:获取dev的所有resv的regions
    .put_resv_regions = arm_smmu_put_resv_regions,//caq:释放上面的regions
    .pgsize_bitmap = -1UL, /* Restricted during device attach */
    };

    
    5、iommu 硬件的注册
    iommu_device 是remap 框架对iommu硬件的一个抽象,框架会要求 **iommu_device_register **来注册一个iommu设备,只要
    走到了这一步,说明iommu硬件初始化完成,同时软件功能上,也被iommu框架所接纳。
    水平有限,如果有错误,请帮忙提醒我。如果您觉得本文对您有帮助,可以点击下面的 推荐 支持一下我。版权所有,需要转发请带上本文源地址,博客一直在更新,欢迎 关注 。
  • 相关阅读:
    彻底弄懂GMT、UTC、时区和夏令时
    揭秘webpack loader
    揭秘webpack plugin
    封装axios
    webpack优化之玩转代码分割和公共代码提取
    node.js操作数据库之MongoDB+mongoose篇
    GitHub项目徽标
    Java中容易遗漏的小知识点( 一 )(为了和小白一样马上要考试的兄弟准备的,希望小白和大家高过不挂)
    QNX Message Passing,一个让人头秃的 IPC BUG
    【百面】02_模型评估
  • 原文地址:https://www.cnblogs.com/10087622blog/p/15492288.html
Copyright © 2020-2023  润新知