iommu 实现了mmap函数,也就是说iommu可以将memory映射到user space。
static int __iommu_mmap_attrs(struct device *dev, struct vm_area_struct *vma,
void *cpu_addr, dma_addr_t dma_addr, size_t size,
unsigned long attrs)
{
struct vm_struct *area;
int ret;
vma->vm_page_prot = __get_dma_pgprot(attrs, vma->vm_page_prot,
is_device_dma_coherent(dev));
if (dma_mmap_from_coherent(dev, vma, cpu_addr, size, &ret))
return ret;
area = find_vm_area(cpu_addr);
if (WARN_ON(!area || !area->pages))
return -ENXIO;
return iommu_dma_mmap(area->pages, size, vma);
}
其中dma_mmap_from_coherent 一般会返回false
因此调用find_vm_area 来查找cpu_addr 来查找对应的vm_struct
struct vm_struct *find_vm_area(const void *addr)
{
struct vmap_area *va;
va = find_vmap_area((unsigned long)addr);
if (va && va->flags & VM_VM_AREA)
return va->vm;
return NULL;
}
函数实现比较简单,继续调用find_vmap_area
static struct vmap_area *__find_vmap_area(unsigned long addr)
{
struct rb_node *n = vmap_area_root.rb_node;
while (n) {
struct vmap_area *va;
va = rb_entry(n, struct vmap_area, rb_node);
if (addr < va->va_start)
n = n->rb_left;
else if (addr >= va->va_end)
n = n->rb_right;
else
return va;
}
return NULL;
}
从__find_vmap_area 中可以看到,kernel中所有的vmap_area 是按照rb tree组织的,这个rb tree的root就是vmap_area_root.rb_node,因此根据虚拟地址就能找到对应的vmap_area,而vmap_area->vm_struct 就返回vm_struct了
找到vm_struct 后就调用iommu_dma_mmap 将vm_struct 中的page插入到vm_area_struct 中
如果没有找到area 或者area->pages为null的话,就返回,负责调用iommu_dma_mmap来做mmap
int iommu_dma_mmap(struct page **pages, size_t size, struct vm_area_struct *vma)
{
unsigned long uaddr = vma->vm_start;
unsigned int i, count = PAGE_ALIGN(size) >> PAGE_SHIFT;
int ret = -ENXIO;
for (i = vma->vm_pgoff; i < count && uaddr < vma->vm_end; i++) {
ret = vm_insert_page(vma, uaddr, pages[i]);
if (ret)
break;
uaddr += PAGE_SIZE;
}
return ret;
}
在iommu_dma_mmap 中从vm_area_struct 开始vm_pgoff,一直到vm_area_struct的结束vm_end。来遍历,对用vm_insert_page将pages 插入
int vm_insert_page(struct vm_area_struct *vma, unsigned long addr,
struct page *page)
{
//addr必须在vma的范围内
if (addr < vma->vm_start || addr >= vma->vm_end)
return -EFAULT;
// page_count 如果大于0,说明有人正在使用
if (!page_count(page))
return -EINVAL;
// 如果已经map了就置flagVM_MIXEDMAP
if (!(vma->vm_flags & VM_MIXEDMAP)) {
BUG_ON(down_read_trylock(&vma->vm_mm->mmap_sem));
BUG_ON(vma->vm_flags & VM_PFNMAP);
vma->vm_flags |= VM_MIXEDMAP;
}
return insert_page(vma, addr, page, vma->vm_page_prot);
}
static int insert_page(struct vm_area_struct *vma, unsigned long addr,
struct page *page, pgprot_t prot)
{
struct mm_struct *mm = vma->vm_mm;
int retval;
pte_t *pte;
spinlock_t *ptl;
retval = -EINVAL;
if (PageAnon(page))
goto out;
retval = -ENOMEM;
flush_dcache_page(page);
pte = get_locked_pte(mm, addr, &ptl);
if (!pte)
goto out;
retval = -EBUSY;
if (!pte_none(*pte))
goto out_unlock;
/* Ok, finally just insert the thing.. */
get_page(page);
inc_mm_counter_fast(mm, mm_counter_file(page));
page_add_file_rmap(page, false);
set_pte_at(mm, addr, pte, mk_pte(page, prot));
retval = 0;
pte_unmap_unlock(pte, ptl);
return retval;
out_unlock:
pte_unmap_unlock(pte, ptl);
out:
return retval;
}
insert_page 这个函数 最重要的就是将page的pte添加到mm_struct。从而完成page的insert.
static int __iommu_mmap_attrs(struct device *dev, struct vm_area_struct *vma,
void *cpu_addr, dma_addr_t dma_addr, size_t size,
unsigned long attrs)
{
struct vm_struct *area;
int ret;
vma->vm_page_prot = __get_dma_pgprot(attrs, vma->vm_page_prot,
is_device_dma_coherent(dev));
if (dma_mmap_from_coherent(dev, vma, cpu_addr, size, &ret))
return ret;
area = find_vm_area(cpu_addr);
if (WARN_ON(!area || !area->pages))
return -ENXIO;
return iommu_dma_mmap(area->pages, size, vma);
}
其中dma_mmap_from_coherent 一般会返回false
因此调用find_vm_area 来查找cpu_addr 来查找对应的vm_struct
struct vm_struct *find_vm_area(const void *addr)
{
struct vmap_area *va;
va = find_vmap_area((unsigned long)addr);
if (va && va->flags & VM_VM_AREA)
return va->vm;
return NULL;
}
函数实现比较简单,继续调用find_vmap_area
static struct vmap_area *__find_vmap_area(unsigned long addr)
{
struct rb_node *n = vmap_area_root.rb_node;
while (n) {
struct vmap_area *va;
va = rb_entry(n, struct vmap_area, rb_node);
if (addr < va->va_start)
n = n->rb_left;
else if (addr >= va->va_end)
n = n->rb_right;
else
return va;
}
return NULL;
}
从__find_vmap_area 中可以看到,kernel中所有的vmap_area 是按照rb tree组织的,这个rb tree的root就是vmap_area_root.rb_node,因此根据虚拟地址就能找到对应的vmap_area,而vmap_area->vm_struct 就返回vm_struct了
找到vm_struct 后就调用iommu_dma_mmap 将vm_struct 中的page插入到vm_area_struct 中
如果没有找到area 或者area->pages为null的话,就返回,负责调用iommu_dma_mmap来做mmap
int iommu_dma_mmap(struct page **pages, size_t size, struct vm_area_struct *vma)
{
unsigned long uaddr = vma->vm_start;
unsigned int i, count = PAGE_ALIGN(size) >> PAGE_SHIFT;
int ret = -ENXIO;
for (i = vma->vm_pgoff; i < count && uaddr < vma->vm_end; i++) {
ret = vm_insert_page(vma, uaddr, pages[i]);
if (ret)
break;
uaddr += PAGE_SIZE;
}
return ret;
}
在iommu_dma_mmap 中从vm_area_struct 开始vm_pgoff,一直到vm_area_struct的结束vm_end。来遍历,对用vm_insert_page将pages 插入
int vm_insert_page(struct vm_area_struct *vma, unsigned long addr,
struct page *page)
{
//addr必须在vma的范围内
if (addr < vma->vm_start || addr >= vma->vm_end)
return -EFAULT;
// page_count 如果大于0,说明有人正在使用
if (!page_count(page))
return -EINVAL;
// 如果已经map了就置flagVM_MIXEDMAP
if (!(vma->vm_flags & VM_MIXEDMAP)) {
BUG_ON(down_read_trylock(&vma->vm_mm->mmap_sem));
BUG_ON(vma->vm_flags & VM_PFNMAP);
vma->vm_flags |= VM_MIXEDMAP;
}
return insert_page(vma, addr, page, vma->vm_page_prot);
}
static int insert_page(struct vm_area_struct *vma, unsigned long addr,
struct page *page, pgprot_t prot)
{
struct mm_struct *mm = vma->vm_mm;
int retval;
pte_t *pte;
spinlock_t *ptl;
retval = -EINVAL;
if (PageAnon(page))
goto out;
retval = -ENOMEM;
flush_dcache_page(page);
pte = get_locked_pte(mm, addr, &ptl);
if (!pte)
goto out;
retval = -EBUSY;
if (!pte_none(*pte))
goto out_unlock;
/* Ok, finally just insert the thing.. */
get_page(page);
inc_mm_counter_fast(mm, mm_counter_file(page));
page_add_file_rmap(page, false);
set_pte_at(mm, addr, pte, mk_pte(page, prot));
retval = 0;
pte_unmap_unlock(pte, ptl);
return retval;
out_unlock:
pte_unmap_unlock(pte, ptl);
out:
return retval;
}
insert_page 这个函数 最重要的就是将page的pte添加到mm_struct。从而完成page的insert.