DMA概述
DMA是一種無需CPU的參加就可以讓外設與系統內存之間進行雙向數據傳輸的硬件機制。它可以使系統CPU從實際的I/O數據傳輸過程中擺脫出來,大大提高系統的吞吐率,并且在傳輸期間,CPU還可以并發(fā)執(zhí)行其他任務。
DMA與cache的一致性
cache用作CPU針對內存的緩存,避免CPU每一次都要與相對來說慢點的內存交互數據,從而來提高數據的訪問速率,而DMA可以用作內存與外設之間傳輸數據的方式,數據不需要經過CPU周轉。
“假設設備驅動程序把一些數據填充到內存緩沖區(qū)中,然后立刻命令硬件設備利用DMA傳送方式讀取該數據。如果DMA訪問這些物理RAM內存單元,而相應的硬件高速緩存行的內容還沒有寫入RAM中,那么硬件設備所讀取的至就是內存緩沖區(qū)中的舊值。”
現在有兩種方法來處理DMA緩沖區(qū):
一致性DMA映射:
書上講的比較抽象,通俗地說就是任何對DMA緩沖區(qū)的改寫都會直接更新到內存中,也稱之為“同步的”或者“一致的”。
流式DMA映射:
根據個人的理解,這里的流即輸入輸出流,我們需要事先指定DMA緩沖區(qū)的方向。
啟動一次流式DMA數據傳輸分為如下步驟(本DMA驅動開發(fā)介紹僅適合S3C2410處理器類型):
1.分配DMA緩沖區(qū)
在DMA設備不采用S/G(分散/聚集)模式的情況下,必須保證緩沖區(qū)是物理上連續(xù)的,linux內核有兩個函數用來分配連續(xù)的內存:kmalloc()和__get_free_pages()。這兩個函數都有分配連續(xù)內存的最大值,kmalloc以分配字節(jié)為單位,最大約為64KB,__get_free_pages()以分配頁為單位,最大能分配2^order數目的頁,order參數的最大值由include/linux/Mmzone.h文件中的MAX_ORDER宏決定(在默認的2.6.18內核版本中,該宏定義為10。也就是說在理論上__get_free_pages函數一次最多能申請1<<10 * 4KB也就是4MB的連續(xù)物理內存,在Xilinx Zynq Linux內核中,該宏定義為11)。
2. 建立流式映射
在對DMA沖區(qū)進行讀寫訪問之后,且在啟動DMA設備傳輸之前,啟用dma_map_single()函數建立流式DMA映射,這兩個函數接受緩沖區(qū)的線性地址作為其參數并返回相應的總線地址。
3. 釋放流式映射
當DMA傳輸結束之后我們需要釋放該映射,這時調用dma_unmap_single()函數。
注意:
(1). 為了避免高速緩存一致性問題,驅動程序在開始從RAM到設備的DMA數據傳輸之前,如果有必要,應該調用dma_sync_single_for_device()函數刷新與DMA緩沖區(qū)對應的高速緩存行。
(2). 從設備到RAM的一次DMA數據傳送完成之前設備驅動程序是不可以訪問內存緩沖區(qū)的,但如果有必要的話,驅動程序在讀緩沖區(qū)之前,應該調用dma_sync_single_for_cpu()函數使相應的硬件高速緩存行無效。
(3). 雖然kmalloc底層也是用__get_free_pages實現的,不過kmalloc對應的釋放緩沖區(qū)函數為kfree,而__get_free_pages對應的釋放緩沖區(qū)函數為free_pages。具體與__get_free_pages有關系的幾個申請與釋放函數如下:
申請函數:
alloc_pages(gfp_mask,order)返回第一個所分配頁框描述符的地址,或者如果分配失敗則返回NULL。__get_free_pages(gfp_mask,order)類似于alloc_pages(),但它返回第一個所分配頁的線性地址。如果需要獲得線性地址對應的頁框號,那么需要調用virt_to_page(addr)宏產生線性地址。
釋放函數:
__free_pages(page,order)這里主要強調page是要釋放緩沖區(qū)的線性首地址所在的頁框號free_pages(page,order)這個函數類似于__free_pages(page,order),但是它接收的參數為要釋放的第一個頁框的線性地址addr
DMA驅動主要數據結構:
1)DMA單個內核緩沖區(qū)數據結構:
typedef struct dma_buf_s {int size; /* buffer size:緩沖大小 */dma_addr_t dma_start; /* starting DMA address :緩沖區(qū)起始物理地址*/int ref; /* number of DMA references 緩沖區(qū)起始虛擬地址*/void *id; /* to identify buffer from outside 標記 */int write; /* 1: buf to write , 0: buf to read DMA讀還是寫*/struct dma_buf_s *next; /* next buf to process 指向下一個緩沖區(qū)結構*/} dma_buf_t;
2)DMA寄存器數據結構:
/* DMA control register structure */typedef struct {volatile u_long DISRC;/源地址寄存器volatile u_long DISRCC;//源控制寄存器volatile u_long DIDST;//目的寄存器volatile u_long DIDSTC;//目的控制寄存器volatile u_long DCON;//DMA控制寄存器volatile u_long DSTAT;//狀態(tài)寄存器volatile u_long DCSRC;//當前源volatile u_long DCDST;//當前目的volatile u_long DMASKTRIG;//觸發(fā)掩碼寄存器} dma_regs_t;
3)DMA設備數據結構
/* DMA device structre */typedef struct {dma_callback_t callback;//DMA操作完成后的回調函數,在中斷處理例程中調用u_long dst;//目的寄存器內容u_long src;//源寄存器內容u_long ctl;//此設備的控制寄存器內容u_long dst_ctl;//目的控制寄存器內容u_long src_ctl;//源控制寄存器內容} dma_device_t;
4)DMA通道數據結構
/* DMA channel structure */typedef struct {dmach_t channel;//通道號:可為0,1,2,3unsigned int in_use; /* Device is allocated 設備是否已*/const char *device_id; /* Device name 設備名*/dma_buf_t *head; /* where to insert buffers 該DMA通道緩沖區(qū)鏈表頭*/dma_buf_t *tail; /* where to remove buffers該DMA通道緩沖區(qū)鏈表尾*/dma_buf_t *curr; /* buffer currently DMA'ed該DMA通道緩沖區(qū)鏈表中的當前緩沖區(qū)*/unsigned long queue_count; /* number of buffers in the queue 鏈表中緩沖區(qū)個數*/int active; /* 1 if DMA is actually processing data 該通道是否已經在使用*/dma_regs_t *regs; /* points to appropriate DMA registers 該通道使用的DMA控制寄存器*/int irq; /* IRQ used by the channel //通道申請的中斷號*/dma_device_t write; /* to write //執(zhí)行讀操作的DMA設備*/dma_device_t read; /* to read 執(zhí)行寫操作的DMA設備*/} s3c2410_dma_t;
DMA驅動主要函數功能分析:
寫一個DMA驅動的主要工作包括:DMA通道申請、DMA中斷申請、控制寄存器設置、掛入DMA等待隊列、清除DMA中斷、釋放DMA通道.
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內核緩沖區(qū)控制結構,并將它插入DMA等待隊列,設置DMA控制寄存器內容,等待DMA操作觸發(fā)
輸入參數:channel 通道號;buf_id,緩沖區(qū)標識
dma_addr_t data DMA數據緩沖區(qū)起始物理地址;
size DMA數據緩沖區(qū)大小;
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驅動程序中一般只需調用以上3個函數即可。可在驅動初始化中調用s3c2410_request_dma,開始DMA傳輸前調用s3c2410_dma_queue_buffer,釋放驅動模塊時調用s3c2410_free_dma。
·················· END ··················
點擊關注公眾號,回復【1024】免費領學習資料