歡迎來到Linux教程網
Linux教程網
Linux教程網
Linux教程網
Linux教程網 >> Linux基礎 >> 關於Linux >> problems from dma

problems from dma

日期:2017/3/2 10:06:18   编辑:關於Linux

在項目驅動過程中會經常用到dma傳輸數據,而dma需要的內存有自己的特點,一般認為需要物理地址連續,並且內存是不可cache的,在linux內核中提供一個供dma所需內存的申請函數dma_alloc_coheren. 如下所述:
dma_alloc_coherent()

dma_alloc_coherent() -- 獲取物理頁,並將該物理頁的總線地址保存於dma_handle,返回該物理頁的虛擬地址

void *
dma_alloc_coherent(struct device *dev, size_t size,
dma_addr_t *dma_handle, gfp_t gfp)
{
void *ret;
if (!dev || *dev->dma_mask >= 0xffffffffUL)
gfp &= ~GFP_DMA;
ret = (void *)__get_free_pages(gfp, get_order(size)); //(1)
if (ret) {
memset(ret, 0, size);
*dma_handle = virt_to_bus(ret); //(2)
}
return ret;
}

(1) 將size轉換成order, 即2^order
(2) 將虛擬地址ret轉換成總線地址

這個函數是一個平台相關的函數,以上是在x86平台的實現細節,從這裡我們可以看到該函數返回值為linux 內核線性地址,所以對於驅動開發過程的mmap函數實現提供了便利。
但是在powerpc平台卻不是這樣,筆者就曾經遇到在將pci驅動從x86平台移植到powerpc平台時出現問題。
首先我們來先看一下兩個平台對於dma內存的處理。
x86:
linux內存區域分為DMA區域,Normal內存區域與高端內存區域,高端內存區域為當物理內存高於768M時使用,一般DMA區域為16M,這段空間由操作系統預留。DMA區域與Normal區域全部使用線性映射,采用邏輯地址使用,高端內存使用內核虛擬地址。其中內核空間的分部為:
物理區--8M隔離--vmalloc區--8k隔離--4M的高端映射區--固定映射區--128k

powerpc:
本節采用freescale的mpc5121芯片為例,內核沒有采用Normal內存區域,只使用ZONE_DMA和ZONE_HIMEM兩種類型的空間,其中ZONE_DMA存放低端內存, ZONE_HIMEN存放高端內存,整個內存不在采用邏輯地址這一概念。所以基於邏輯地址的操作沒有可移植性。

下面看下具體的區別:
void * __dma_alloc_coherent(size_t size, dma_addr_t *handle, gfp_t gfp)
{
//物理空間頁的申請
page = alloc_pages(gfp, order);

//對物理空間進行清零cache
{
unsigned long kaddr = (unsigned long)page_address(page);
memset(page_address(page), 0, size);
flush_dcache_range(kaddr, kaddr + size);
}

//申請虛擬空間
c = vm_region_alloc(&consistent_head, size, gfp &
~(__GFP_DMA | __GFP_HIGHMEM));

//實現虛擬地址與物理地址映射
if (c) {
unsigned long vaddr = c->vm_start;
pte_t *pte = consistent_pte + CONSISTENT_OFFSET(vaddr);
struct page *end = page + (1 << order);

split_page(page, order);

/*
* Set the "dma handle"
*/
*handle = page_to_bus(page);

do {
BUG_ON(!pte_none(*pte));

SetPageReserved(page);
set_pte_at(&init_mm, vaddr,
pte, mk_pte(page, pgprot_noncached(PAGE_KERNEL)));
page++;
pte++;
vaddr += PAGE_SIZE;
} while (size -= PAGE_SIZE);

// 返回值為內核虛擬地址。
return (void *)c->vm_start

Copyright © Linux教程網 All Rights Reserved