linux Coherent dma 實現
原創 shenhuxi_yu 最後發佈於2019-05-21 01:15:37 閱讀數 285 收藏
分類專欄: LINUX ARM C語言
版權聲明:本文爲博主原創文章,遵循 CC 4.0 BY-SA 版權協議,轉載請附上原文出處鏈接和本聲明。
本文鏈接:https://blog.csdn.net/shenhuxi_yu/article/details/90247463
收起
linux 4.9 armv8
void *dma_alloc_coherent(struct device *dev, size_t size,dma_addr_t *dma_handle, gfp_t flag)
參數:dev device 結構體中有dma相關的控制比如dma_mask,dma_handle 作實參傳遞返回的pa,flag分配memory時的控制選項比如GFP_KERNEL(允許睡眠) GFP_ATOMIC(原子分配)
返回值:cpu看到的va
dma_alloc_coherent(struct device *dev, size_t size,dma_addr_t *dma_handle, gfp_t flag)
--> dma_alloc_attrs(struct device *dev, size_t size, dma_addr_t *dma_handle, gfp_t flag, unsigned long attrs) unsigned long attrs)
{
struct dma_map_ops *ops = get_dma_ops(dev);
//get swiotlb_dma_ops
void *cpu_addr;
if (dma_alloc_from_coherent(dev, size, dma_handle, &cpu_addr))
return cpu_addr;
//dma_alloc_from_coherent() - try to allocate memory from the per-device coherent area 目前還沒遇到perdevice的dma are
cpu_addr = ops->alloc(dev, size, dma_handle, flag, attrs);
//主要操作調用swiotlb_dam_ops的alloc callback
debug_dma_alloc_coherent(dev, size, *dma_handle, cpu_addr);
return cpu_addr;
}
--->static struct dma_map_ops swiotlb_dma_ops = {
.alloc = __dma_alloc,
.free = __dma_free,
.mmap = __swiotlb_mmap,
.get_sgtable = __swiotlb_get_sgtable,
.map_page = __swiotlb_map_page,
.unmap_page = __swiotlb_unmap_page,
.map_sg = __swiotlb_map_sg_attrs,
.unmap_sg = __swiotlb_unmap_sg_attrs,
};
---->static void *__dma_alloc(struct device *dev, size_t size,
dma_addr_t *dma_handle, gfp_t flags,
unsigned long attrs)
{
struct page *page;
void *ptr, *coherent_ptr;
bool coherent = is_device_dma_coherent(dev);
//device 的archdata裏面有個dam_coherent屬性,沒關注這個目的是什麼,常見的device這個屬性是false
pgprot_t prot = __get_dma_pgprot(attrs, PAGE_KERNEL, false);
//dma memory cache 屬性,會寫到頁表項中這是none cache 的關鍵
size = PAGE_ALIGN(size);
//申請的memory是page align
ptr = __dma_alloc_coherent(dev, size, dma_handle, flags, attrs);
//調用get_free_pages申請空間建立頁表
if (!ptr)
goto no_mem;
/* no need for non-cacheable mapping if coherent */
if (coherent)
return ptr;
/* remove any dirty cache lines on the kernel alias */
__dma_flush_area(ptr, size);
/* create a coherent mapping */
page = virt_to_page(ptr);
coherent_ptr = dma_common_contiguous_remap(page, size, VM_USERMAP,
prot, NULL);
//coherent_ptr 最終得到的coherent buf 的va
return NULL;
}
----->static void *__dma_alloc_coherent(struct device *dev, size_t size,
dma_addr_t *dma_handle, gfp_t flags,
unsigned long attrs)
{
//不考慮cma 直接調用swiotlb的接口
return swiotlb_alloc_coherent(dev, size, dma_handle, flags);
}
------>void *
swiotlb_alloc_coherent(struct device *hwdev, size_t size,
dma_addr_t *dma_handle, gfp_t flags)
{
dma_addr_t dev_addr;
void *ret;
int order = get_order(size);
u64 dma_mask = DMA_BIT_MASK(32);
if (hwdev && hwdev->coherent_dma_mask)
dma_mask = hwdev->coherent_dma_mask;
ret = (void *)__get_free_pages(flags, order);
//從buddy系統中獲取order大小page,返回虛擬地址,最大限制是4M
if (ret) {
dev_addr = swiotlb_virt_to_bus(hwdev, ret);
//虛擬地址 ret轉成物理地址,這裏是最終得到的PA,後面會配置這個地址給具體的dma hw使用
if (dev_addr + size - 1 > dma_mask) {
//dma_mask hw決定的dma的尋址範圍,目前手邊的平臺都可以達到32bit,高端soc有可能會超過4G空間,dram 這邊可以配置對地址補位
/*
* The allocated memory isn't reachable by the device.
*/
free_pages((unsigned long) ret, order);
ret = NULL;
}
}
if (!ret) {
//獲取物理內存失敗
/*
* We are either out of memory or the device can't DMA to
* GFP_DMA memory; fall back on map_single(), which
* will grab memory from the lowest available address range.
*/
}
}
*dma_handle = dev_addr;
memset(ret, 0, size);
return ret;
}
------>/*
* remaps an allocated contiguous region into another vm_area.
* Cannot be used in non-sleeping contexts
*/
//在__dma_alloc 函數調用到該函數時已經通過get_free_pages拿到了vm
void *dma_common_contiguous_remap(struct page *page, size_t size,
unsigned long vm_flags,
pgprot_t prot, const void *caller)
{
int i;
struct page **pages;
void *ptr;
unsigned long pfn;
//申請頁表空間,用來構建多級頁表
pages = kmalloc(sizeof(struct page *) << get_order(size), GFP_KERNEL);
if (!pages)
return NULL;
//構建一級頁表
for (i = 0, pfn = page_to_pfn(page); i < (size >> PAGE_SHIFT); i++)
pages[i] = pfn_to_page(pfn + i);
ptr = dma_common_pages_remap(pages, size, vm_flags, prot, caller);
kfree(pages);
return ptr;
}
------>/*
* remaps an array of PAGE_SIZE pages into another vm_area
* Cannot be used in non-sleeping contexts
*/
void *dma_common_pages_remap(struct page **pages, size_t size,
unsigned long vm_flags, pgprot_t prot,
const void *caller)
{
struct vm_struct *area;
area = get_vm_area_caller(size, vm_flags, caller);
if (!area)
return NULL;
area->pages = pages;
//vm描述符與page描述符綁定,map_vm_are
if (map_vm_area(area, prot, pages)) {
vunmap(area->addr);
return NULL;
}
return area->addr;
}
------>struct vm_struct *get_vm_area_caller(unsigned long size, unsigned long flags,
const void *caller)
{
return __get_vm_area_node(size, 1, flags, VMALLOC_START, VMALLOC_END,
NUMA_NO_NODE, GFP_KERNEL, caller);
}
------>static struct vm_struct *__get_vm_area_node(unsigned long size,
unsigned long align, unsigned long flags, unsigned long start,
unsigned long end, int node, gfp_t gfp_mask, const void *caller)
{
struct vmap_area *va;
struct vm_struct *area;
BUG_ON(in_interrupt());
size = PAGE_ALIGN(size);
area = kzalloc_node(sizeof(*area), gfp_mask & GFP_RECLAIM_MASK, node);
if (unlikely(!area))
return NULL;
/*
* Allocate a region of KVA of the specified size and alignment, within the
* vstart and vend.
*/
//從VMALLOC區拿到一塊vm出來
va = alloc_vmap_area(size, align, start, end, node, gfp_mask);
if (IS_ERR(va)) {
kfree(area);
return NULL;
}
setup_vmalloc_vm(area, va, flags, caller);
//vmap_area vm_struct 建立之前申請到的這段vm的描述
return area;
}
------>int map_vm_area(struct vm_struct *area, pgprot_t prot, struct page **pages)
{
unsigned long addr = (unsigned long)area->addr;
unsigned long end = addr + get_vm_area_size(area);
int err;
err = vmap_page_range(addr, end, prot, pages);
return err > 0 ? 0 : err;
}
------>static int vmap_page_range(unsigned long start, unsigned long end,
pgprot_t prot, struct page **pages)
{
int ret;
ret = vmap_page_range_noflush(start, end, prot, pages);
flush_cache_vmap(start, end);
return ret;
}
------>/*
* Set up page tables in kva (addr, end). The ptes shall have prot "prot", and
* will have pfns corresponding to the "pages" array.
*
* Ie. pte at addr+N*PAGE_SIZE shall point to pfn corresponding to pages[N]
*/
//用申請到的va區域構建page table
static int vmap_page_range_noflush(unsigned long start, unsigned long end,
pgprot_t prot, struct page **pages)
{
pgd_t *pgd;
unsigned long next;
unsigned long addr = start;
int err = 0;
int nr = 0;
BUG_ON(addr >= end);
pgd = pgd_offset_k(addr);
do {
next = pgd_addr_end(addr, end);
err = vmap_pud_range(pgd, addr, next, prot, pages, &nr);
if (err)
return err;
} while (pgd++, addr = next, addr != end);
//從pgd 到 pud 到pmd 最後到pte 填充多級頁表項,以兩級爲例
return nr;
}
------->static int vmap_pte_range(pmd_t *pmd, unsigned long addr,
unsigned long end, pgprot_t prot, struct page **pages, int *nr)
{
pte_t *pte;
/*
* nr is a running index into the array which helps higher level
* callers keep track of where we're up to.
*/
pte = pte_alloc_kernel(pmd, addr);
if (!pte)
return -ENOMEM;
do {
struct page *page = pages[*nr];
if (WARN_ON(!pte_none(*pte)))
return -EBUSY;
if (WARN_ON(!page))
return -ENOMEM;
set_pte_at(&init_mm, addr, pte, mk_pte(page, prot));
(*nr)++;
} while (pte++, addr += PAGE_SIZE, addr != end);
//填充pte 的property字段爲none cache none buf, cache miss 的情況下,直接從主存讀取數據,如果是可cache able屬性的頁表項,發生cached miss 後會先把主存的內容放到cache 成爲cache linefill 操作
return 0;
}
利用debug工具看到的頁表內容如下頁表項屬性 I:non-cacheable O:non-cacheable
dma coherent buf 的建立過程有多次memeory的申請
1. __get_free_pages: 直接從buddy系統裏獲取2order的連續物理頁,也是最終傳給dma hw 的pa,返回獲取到的第一個物理頁的起始va
2.alloc_vmap_area:從VMALLOC區拿出一塊VM,是最終申請到的coherent buf 的va
struct vm_struct {
struct vm_struct *next;
void *addr;
unsigned long size;
unsigned long flags;
struct page **pages;
unsigned int nr_pages;
phys_addr_t phys_addr;
const void *caller;
};
3. dma_common_contiguous_remap 調用kmalloc 申請頁描述符空間,用alloc_vmap_area 拿到的va構造page table
mem_map 管理node內所有物理page的全局數組
__get_free_pages 後就拿到物理連續的va 與pa而且low mem區kernel init的時候就已經建立內核頁表, 爲什麼還要用alloc_vmap_area在去申請va 建立頁表?
用debug工具看到的信息如下
第一次的建立的地址映射虛擬地址是0xffffffc08907200 該頁表項映射的size 是2M
第二次建立的頁表映射虛擬地址0xffffff800831000 頁表項映射的size是4k,debug 工具看一直到真正使用過後才從dump到映射關係
————————————————
版權聲明:本文爲CSDN博主「shenhuxi_yu」的原創文章,遵循 CC 4.0 BY-SA 版權協議,轉載請附上原文出處鏈接及本聲明。
原文鏈接:https://blog.csdn.net/shenhuxi_yu/article/details/90247463