iommu的get_sgtable 函数实现如下:
static int __iommu_get_sgtable(struct device *dev, struct sg_table *sgt,
void *cpu_addr, dma_addr_t dma_addr,
size_t size, unsigned long attrs)
{
unsigned int count = PAGE_ALIGN(size) >> PAGE_SHIFT;
struct vm_struct *area = find_vm_area(cpu_addr);
if (WARN_ON(!area || !area->pages))
return -ENXIO;
return sg_alloc_table_from_pages(sgt, area->pages, count, 0, size,
GFP_KERNEL);
}
在__iommu_get_sgtable 中首先将需要申请的size转换成page number,即总共需要申请多少个page,并按page size对其。
unsigned int count = PAGE_ALIGN(size) >> PAGE_SHIFT;
然后通过find_vm_area 找到cpu_addr 对应的vm_struct,也就是落在vmalloc区域。找到这个vm_struct *area 就可以通过area->pages得到page address
检查area和area->pages 不为null后调用sg_alloc_table_from_pages,这个函数会申请并初始化一个sg table
int sg_alloc_table_from_pages(struct sg_table *sgt,
struct page **pages, unsigned int n_pages,
unsigned long offset, unsigned long size,
gfp_t gfp_mask)
{
unsigned int chunks;
unsigned int i;
unsigned int cur_page;
int ret;
struct scatterlist *s;
/* compute number of contiguous chunks */
chunks = 1;
for (i = 1; i < n_pages; ++i)
if (page_to_pfn(pages[i]) != page_to_pfn(pages[i - 1]) + 1)
++chunks;
ret = sg_alloc_table(sgt, chunks, gfp_mask);
if (unlikely(ret))
return ret;
/* merging chunks and putting them into the scatterlist */
cur_page = 0;
for_each_sg(sgt->sgl, s, sgt->orig_nents, i) {
unsigned long chunk_size;
unsigned int j;
/* look for the end of the current chunk */
for (j = cur_page + 1; j < n_pages; ++j)
if (page_to_pfn(pages[j]) !=
page_to_pfn(pages[j - 1]) + 1)
break;
chunk_size = ((j - cur_page) << PAGE_SHIFT) - offset;
sg_set_page(s, pages[cur_page], min(size, chunk_size), offset);
size -= chunk_size;
offset = 0;
cur_page = j;
}
return 0;
}
在sg_alloc_table_from_pages 中首先通过
for (i = 1; i < n_pages; ++i)
if (page_to_pfn(pages[i]) != page_to_pfn(pages[i - 1]) + 1)
++chunks;
计算有多少个连续的page.例如pfg=1,2,5,6的话,这个chunks就等于2.这个值记录在chunks中,然后根据chunks 来申请sg_table *sgt
然后这个for_each_sg(sgt->sgl, s, sgt->orig_nents, i) 会变量所有的chunks。这里的 sgt->orig_nents就等于chunks
/* look for the end of the current chunk */
for (j = cur_page + 1; j < n_pages; ++j)
if (page_to_pfn(pages[j]) !=
page_to_pfn(pages[j - 1]) + 1)
break;
这段code找到从page pfn 0开始的第一个chunks的size
chunk_size = ((j - cur_page) << PAGE_SHIFT) - offset;
上面的code计算当前chunk szize
最后通过 sg_set_page(s, pages[cur_page], min(size, chunk_size), offset);将chunks的offset和size存到到scatterlist *sg
static inline void sg_set_page(struct scatterlist *sg, struct page *page,
unsigned int len, unsigned int offset)
{
sg_assign_page(sg, page);
sg->offset = offset;
sg->length = len;
}
for循环继续直到遍历所有的chunk
转载请注明原文地址: https://ju.6miu.com/read-9996.html