DMA(直接內存訪問)要求物理地址連續且通常不可緩存。Linux 提供 dma_alloc_coherent 分配此類內存,但不同架構實現差異巨大。本文對比 x86 與 PowerPC(以 Freescale MPC5121 為例)的實現差異,幫助驅動移植時避坑。
void *dma_alloc_coherent(struct device *dev, size_t size,
dma_addr_t *dma_handle, gfp_t gfp)
{
void *ret = (void *)__get_free_pages(gfp, get_order(size));
if (ret) {
memset(ret, 0, size);
*dma_handle = virt_to_bus(ret); // 邏輯地址 → 總線地址
}
return ret; // 返回內核線性地址
}
特點:
mmap 實現void *__dma_alloc_coherent(size_t size, dma_addr_t *handle, gfp_t gfp)
{
struct page *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);
// 從 consistent 區分配虛擬地址空間
struct vm_region *c = vm_region_alloc(&consistent_head, size, ...);
unsigned long vaddr = c->vm_start;
pte_t *pte = consistent_pte + CONSISTENT_OFFSET(vaddr);
split_page(page, order);
*handle = page_to_bus(page); // 設置 DMA handle
do {
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; // 返回內核虛擬地址
}
關鍵差異:
set_pte_at)alloc_pages),再分配虛擬區域(vm_region_alloc),最後建立映射flush_dcache_range 並設置 pgprot_noncached 頁保護屬性consistent_head 區域分配虛擬地址virt_to_phys/phys_to_virt 可用於 DMA 緩衝區dma_alloc_coherent/dma_free_coherent APIdma_handle 為總線地址,而非 CPU 物理地址x86 依賴線性映射簡化 DMA 分配,PowerPC 則通過顯式頁表構建一致性映射區。理解這一本質差異,對編寫可移植驅動至關重要。