轉載於:
http://www.codexiu.cn/linux/blog/23237/
DMA是一種無需CPU的參與就可以讓外設和系統內存之間進行雙向數據傳輸的硬件機制。使用DMA可以使系統CPU從實際的I/O數據傳輸過程中擺脫出來,從而大大提高系統的吞吐率。DMA經常與硬件體系結構特別是外設的總線技術密切相關。
一、DMA控制器硬件結構
DMA允許外圍設備和主內存之間直接傳輸 I/O 數據, DMA 依賴於系統。每一種體系結構DMA傳輸不同,編程接口也不同。
數據傳輸可以以兩種方式觸發:一種軟件請求數據,另一種由硬件異步傳輸。
a -- 軟件請求數據
調用的步驟可以概括如下(以read為例):
(1)在進程調用 read 時,驅動程序的方法分配一個 DMA 緩沖區,隨后指示硬件傳送它的數據。進程進入睡眠。
(2)硬件將數據寫入 DMA 緩沖區並在完成時產生一個中斷。
(3)中斷處理程序獲得輸入數據,應答中斷,最后喚醒進程,該進程現在可以讀取數據了。
b -- 由硬件異步傳輸
在 DMA 被異步使用時發生的。以數據采集設備為例:
(1)硬件發出中斷來通知新的數據已經到達。
(2)中斷處理程序分配一個DMA緩沖區。
(3)外圍設備將數據寫入緩沖區,然后在完成時發出另一個中斷。
(4)處理程序利用DMA分發新的數據,喚醒任何相關進程。
網卡傳輸也是如此,網卡有一個循環緩沖區(通常叫做 DMA 環形緩沖區)建立在與處理器共享的內存中。每一個輸入數據包被放置在環形緩沖區中下一個可用緩沖區,並且發出中斷。然后驅動程序將網絡數據包傳給內核的其它部分處理,並在環形緩沖區中放置一個新的 DMA 緩沖區。
驅動程序在初始化時分配DMA緩沖區,並使用它們直到停止運行。
二、DMA通道使用的地址
DMA通道用dma_chan結構數組表示,這個結構在kernel/dma.c中,列出如下:
struct dma_chan {
int lock;
const char *device_id;
};
static struct dma_chan dma_chan_busy[MAX_DMA_CHANNELS] = {
[4] = { 1, "cascade" },
};
如果dma_chan_busy[n].lock != 0表示忙,DMA0保留為DRAM更新用,DMA4用作級聯。DMA 緩沖區的主要問題是,當它大於一頁時,它必須占據物理內存中的連續頁。
由於DMA需要連續的內存,因而在引導時分配內存或者為緩沖區保留物理 RAM 的頂部。在引導時給內核傳遞一個"mem="參數可以保留 RAM 的頂部。例如,如果系統有 32MB 內存,參數"mem=31M"阻止內核使用最頂部的一兆字節。稍后,模塊可以使用下面的代碼來訪問這些保留的內存:
dmabuf = ioremap( 0x1F00000 /* 31M */, 0x100000 /* 1M */);
分配 DMA 空間的方法,代碼調用 kmalloc(GFP_ATOMIC) 直到失敗為止,然后它等待內核釋放若干頁面,接下來再一次進行分配。最終會發現由連續頁面組成的DMA 緩沖區的出現。
一個使用 DMA 的設備驅動程序通常會與連接到接口總線上的硬件通訊,這些硬件使用物理地址,而程序代碼使用虛擬地址。基於 DMA 的硬件使用總線地址而不是物理地址,有時,接口總線是通過將 I/O 地址映射到不同物理地址的橋接電路連接的。甚至某些系統有一個頁面映射方案,能夠使任意頁面在外圍總線上表現為連續的。
當驅動程序需要向一個 I/O 設備(例如擴展板或者DMA控制器)發送地址信息時,必須使用 virt_to_bus 轉換,在接受到來自連接到總線上硬件的地址信息時,必須使用 bus_to_virt 了。
三、DMA操作函數
寫一個DMA驅動的主要工作包括:DMA通道申請、DMA中斷申請、控制寄存器設置、掛入DMA等待隊列、清除DMA中斷、釋放DMA通道
因為 DMA 控制器是一個系統級的資源,所以內核協助處理這一資源。內核使用 DMA 注冊表為 DMA 通道提供了請求/釋放機制,並且提供了一組函數在 DMA 控制器中配置通道信息。
以下具體分析關鍵函數(linux/arch/arm/mach-s3c2410/dma.c)
int s3c2410_request_dma(const char *device_id, dmach_t channel,
dma_callback_t write_cb, dma_callback_t read_cb) (s3c2410_dma_queue_buffer);
/*
函數描述:申請某通道的DMA資源,填充s3c2410_dma_t 數據結構的內容,申請DMA中斷。
輸入參數:device_id DMA 設備名;channel 通道號;
write_cb DMA寫操作完成的回調函數;read_cb DMA讀操作完成的回調函數
輸出參數:若channel通道已使用,出錯返回;否則,返回0
*/
int s3c2410_dma_queue_buffer(dmach_t channel, void *buf_id,
dma_addr_t data, int size, int write) (s3c2410_dma_stop);
/*
函數描述:這是DMA操作最關鍵的函數,它完成了一系列動作:分配並初始化一個DMA內核緩沖區控制結構,並將它插入DMA等待隊列,設置DMA控制寄存器內容,等待DMA操作觸發
輸入參數: channel 通道號;buf_id,緩沖區標識
dma_addr_t data DMA數據緩沖區起始物理地址;size DMA數據緩沖區大小;write 是寫還是讀操作
輸出參數:操作成功,返回0;否則,返回錯誤號
*/
int s3c2410_dma_stop(dmach_t channel)
//函數描述:停止DMA操作。
int s3c2410_dma_flush_all(dmach_t channel)
//函數描述:釋放DMA通道所申請的所有內存資源
void s3c2410_free_dma(dmach_t channel)
//函數描述:釋放DMA通道
四、DMA映射
一個DMA映射就是分配一個 DMA 緩沖區並為該緩沖區生成一個能夠被設備訪問的地址的組合操作。一般情況下,簡單地調用函數virt_to_bus 就設備總線上的地址,但有些硬件映射寄存器也被設置在總線硬件中。映射寄存器(mapping register)是一個類似於外圍設備的虛擬內存等價物。在使用這些寄存器的系統上,外圍設備有一個相對較小的、專用的地址區段,可以在此區段執行 DMA。通過映射寄存器,這些地址被重映射到系統 RAM。映射寄存器具有一些好的特性,包括使分散的頁面在設備地址空間看起來是連續的。但不是所有的體系結構都有映射寄存器,特別地,PC 平台沒有映射寄存器。
在某些情況下,為設備設置有用的地址也意味着需要構造一個反彈(bounce)緩沖區。例如,當驅動程序試圖在一個不能被外圍設備訪問的地址(一個高端內存地址)上執行 DMA 時,反彈緩沖區被創建。然后,按照需要,數據被復制到反彈緩沖區,或者從反彈緩沖區復制。
根據 DMA 緩沖區期望保留的時間長短,PCI 代碼區分兩種類型的 DMA 映射:
a -- 一致 DMA 映射
它們存在於驅動程序的生命周期內。一個被一致映射的緩沖區必須同時可被 CPU 和外圍設備訪問,這個緩沖區被處理器寫時,可立即被設備讀取而沒有cache效應,反之亦然,使用函數pci_alloc_consistent建立一致映射。
b -- 流式 DMA映射
流式DMA映射是為單個操作進行的設置。它映射處理器虛擬空間的一塊地址,以致它能被設備訪問。應盡可能使用流式映射,而不是一致映射。這是因為在支持一致映射的系統上,每個 DMA 映射會使用總線上一個或多個映射寄存器。具有較長生命周期的一致映射,會獨占這些寄存器很長時間――即使它們沒有被使用。使用函數dma_map_single建立流式映射。
1、建立一致 DMA 映射
函數pci_alloc_consistent處理緩沖區的分配和映射,函數分析如下(在include/asm-generic/pci-dma-compat.h中):
[cpp] view
plain copy
static inline void *pci_alloc_consistent(struct pci_dev *hwdev,
size_t size, dma_addr_t *dma_handle)
{
return dma_alloc_coherent(hwdev == NULL ? NULL : &hwdev->dev,
size, dma_handle, GFP_ATOMIC);
}
結構dma_coherent_mem定義了DMA一致性映射的內存的地址、大小和標識等。結構dma_coherent_mem列出如下(在arch/i386/kernel/pci-dma.c中):
struct dma_coherent_mem {
void *virt_base;
u32 device_base;
int size;
int flags;
unsigned long *bitmap;
};
函數dma_alloc_coherent分配size字節的區域的一致內存,得到的dma_handle是指向分配的區域的地址指針,這個地址作為區域的物理基地址。dma_handle是與總線一樣的位寬的無符號整數。 函數dma_alloc_coherent分析如下(在arch/i386/kernel/pci-dma.c中):
void *dma_alloc_coherent(struct device *dev, size_t size,
dma_addr_t *dma_handle, int gfp)
{
void *ret;
//若是設備,得到設備的dma內存區域,即mem= dev->dma_mem
struct dma_coherent_mem *mem = dev ? dev->dma_mem : NULL;
int order = get_order(size);//將size轉換成order,即
//忽略特定的區域,因而忽略這兩個標識
gfp &= ~(__GFP_DMA | __GFP_HIGHMEM);
if (mem) {//設備的DMA映射,mem= dev->dma_mem
//找到mem對應的頁
int page = bitmap_find_free_region(mem->bitmap, mem->size,
order);
if (page >= 0) {
*dma_handle = mem->device_base + (page << PAGE_SHIFT);
ret = mem->virt_base + (page << PAGE_SHIFT);
memset(ret, 0, size);