smmu 学习笔记之map

    xiaoxiao2021-03-25  106

    iommu 调用__iommu_map_page 来映射一个page static dma_addr_t __iommu_map_page(struct device *dev, struct page *page,                    unsigned long offset, size_t size,                    enum dma_data_direction dir,                    unsigned long attrs) {     bool coherent = is_device_dma_coherent(dev);     int prot = dma_direction_to_prot(dir, coherent);     dma_addr_t dev_addr = iommu_dma_map_page(dev, page, offset, size, prot);     if (!iommu_dma_mapping_error(dev, dev_addr) &&         (attrs & DMA_ATTR_SKIP_CPU_SYNC) == 0)         __iommu_sync_single_for_device(dev, dev_addr, size, dir);     return dev_addr; } 在__iommu_map_page 中首先通过is_device_dma_coherent 判断是否coherent。根据coherent 和 dir 来决定prot int dma_direction_to_prot(enum dma_data_direction dir, bool coherent) {     int prot = coherent ? IOMMU_CACHE : 0;     switch (dir) {     case DMA_BIDIRECTIONAL:         return prot | IOMMU_READ | IOMMU_WRITE;     case DMA_TO_DEVICE:         return prot | IOMMU_READ;     case DMA_FROM_DEVICE:         return prot | IOMMU_WRITE;     default:         return 0;     } } 如果是coherent prot就等于IOMMU_CACHE,再根据dir 来决定当前page是IOMMU_READ 还是IOMMU_WRITE。 然后调用iommu_dma_map_page 返回一个用于dma的address dev_addr。 dma_addr_t iommu_dma_map_page(struct device *dev, struct page *page,         unsigned long offset, size_t size, int prot) {     dma_addr_t dma_addr;     struct iommu_domain *domain = iommu_get_domain_for_dev(dev);     struct iova_domain *iovad = cookie_iovad(domain);     phys_addr_t phys = page_to_phys(page) + offset;     size_t iova_off = iova_offset(iovad, phys);     size_t len = iova_align(iovad, size + iova_off);     struct iova *iova = __alloc_iova(domain, len, dma_get_mask(dev));     if (!iova)         return DMA_ERROR_CODE;     dma_addr = iova_dma_addr(iovad, iova);     if (iommu_map(domain, dma_addr, phys - iova_off, len, prot)) {         __free_iova(iovad, iova);         return DMA_ERROR_CODE;     }     return dma_addr + iova_off; } 在iommu_dma_map_page 中包含要映射的page,offset,size等,从我打log的情况看这里的offset基本都是0 从iommu_get_domain_for_dev(dev);中得到struct iommu_domain *domain struct iommu_domain *iommu_get_domain_for_dev(struct device *dev) {     struct iommu_domain *domain;     struct iommu_group *group;     group = iommu_group_get(dev);     /* FIXME: Remove this when groups a mandatory for iommu drivers */     if (group == NULL)         return NULL;     domain = group->domain;     iommu_group_put(group);     return domain; } iommu_get_domain_for_dev 又是通过iommu_group_get得到iommu_group *group再通过group->domain得到iommu_domain。 struct iommu_group *iommu_group_get(struct device *dev) {     struct iommu_group *group = dev->iommu_group;     if (group)         kobject_get(group->devices_kobj);     return group; } 而iommu_group 包含在device这个结构体中。 总结一下,就是device 这个结构体包含iommu_group,iommu_group 又包含iommu_domain 得到这个iommu_domain 后就可以通过cookie_iovad(domain); static inline struct iova_domain *cookie_iovad(struct iommu_domain *domain) {     return &((struct iommu_dma_cookie *)domain->iova_cookie)->iovad; } 得到iovad。 这个iova中有一个变量granule,表示这个iova最小的对齐size,主要用过计算需要映射的page的地址和这个最小对齐的size是否有差值,有点的话,这个值就是    size_t iova_off = iova_offset(iovad, phys);通过根据granule 计算需要申请的size是否对齐    size_t len = iova_align(iovad, size + iova_off); 随后就根据len申请一个虚拟地址,也就是iova。     struct iova *iova = __alloc_iova(domain, len, dma_get_mask(dev)); 这里的__alloc_iova 主要会按照len在rb tree中返回一个虚拟地址。     dma_addr = iova_dma_addr(iovad, iova); 最后通过iova_dma_addr 将iova 转成硬件用的dma地址,注意这里的dma_addr 和 page 已经代表的是不同的地址了,从我打印的结果看也不一致。 得到这个dma地址后,就通过iommu_map 来映射这块虚拟地址,注意前面只是计算page对应的虚拟地址,没有map,也就是这段虚拟地址还没有和page建立关系。通过iommu_map 后page的地址就和dma_addr建立类似mmu的关系了。
    转载请注明原文地址: https://ju.6miu.com/read-20991.html

    最新回复(0)