smmu 学习笔记之map_page

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的关系了。




你可能感兴趣的:(Linux,源码分析)