linux Coherent dma 實現

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

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章