DMA概述
DMA是一種無需CPU的參加就可以讓外設與系統記憶體之間進行雙向資料傳輸的硬體機制。它可以使系統CPU從實際的I/O資料傳輸過程中擺脫出來,大大提高系統的吞吐率,並且在傳輸期間,CPU還可以併發執行其他任務。
DMA與cache的一致性
cache用作CPU針對記憶體的快取,避免CPU每一次都要與相對來說慢點的記憶體互動資料,從而來提高資料的訪問速率,而DMA可以用作記憶體與外設之間傳輸資料的方式,資料不需要經過CPU週轉。
“假設裝置驅動程式把一些資料填充到記憶體緩衝區中,然後立刻命令硬體裝置利用DMA傳送方式讀取該資料。如果DMA訪問這些物理RAM記憶體單元,而相應的硬體快取記憶體行的內容還沒有寫入RAM中,那麼硬體裝置所讀取的至就是記憶體緩衝區中的舊值。”
現在有兩種方法來處理DMA緩衝區:
一致性DMA對映:
書上講的比較抽象,通俗地所就是任何對DMA緩衝區的改寫都會直接更新到記憶體中,也稱之為“同步的”或者“一致的”。
流式DMA對映:
根據個人的理解,這裡的流即輸入輸出流,我們需要事先指定DMA緩衝區的方向。
啟動一次流式DMA資料傳輸分為如下步驟(本DMA驅動開發介紹僅適合S3C2410處理器型別):
1. 分配DMA緩衝區
在DMA裝置不採用S/G(分散/聚集)模式的情況下,必須保證緩衝區是物理上連續的,linux核心有兩個函式用來分配連續的記憶體:kmalloc()和__get_free_pages()。這兩個函式都有分配連續記憶體的最大值,kmalloc以分配位元組為單位,最大約為64KB,__get_free_pages()以分配頁為單位,最大能分配2^order數目的頁,order引數的最大值由include/linux/Mmzone.h檔案中的MAX_ORDER巨集決定(在預設的2.6.18核心版本中,該巨集定義為10。也就是說在理論上__get_free_pages函式一次最多能申請1<<10 * 4KB也就是4MB的連續實體記憶體,在Xilinx Zynq Linux核心中,該巨集定義為11)。
2. 建立流式對映
在對DMA衝區進行讀寫訪問之後,且在啟動DMA裝置傳輸之前,啟用dma_map_single()函式建立流式DMA對映,這兩個函式接受緩衝區的線性地址作為其引數並返回相應的匯流排地址。
3. 釋放流式對映
當DMA傳輸結束之後我們需要釋放該對映,這時呼叫dma_unmap_single()函式。
注意:
(1). 為了避免快取記憶體一致性問題,驅動程式在開始從RAM到裝置的DMA資料傳輸之前,如果有必要,應該呼叫dma_sync_single_for_device()函式重新整理與DMA緩衝區對應的快取記憶體行。
(2). 從裝置到RAM的一次DMA資料傳送完成之前裝置驅動程式是不可以訪問記憶體緩衝區的,但如果有必要的話,驅動程式在讀緩衝區之前,應該呼叫dma_sync_single_for_cpu()函式使相應的硬體快取記憶體行無效。
(3). 雖然kmalloc底層也是用__get_free_pages實現的,不過kmalloc對應的釋放緩衝區函式為kfree,而__get_free_pages對應的釋放緩衝區函式為free_pages。具體與__get_free_pages有關係的幾個申請與釋放函式如下:
申請函式:
1 2 3 4 5 |
alloc_pages(gfp_mask,order) //返回第一個所分配頁框描述符的地址,或者如果分配失敗則返回NULL。 __get_free_pages(gfp_mask,order) //類似於alloc_pages(),但它返回第一個所分配頁的線性地址。如果需要獲得線性地址對應的頁框號,那麼需要呼叫virt_to_page(addr)巨集產生線性地址。 |
釋放函式:
1 2 3 4 5 |
__free_pages(page,order) //這裡主要強調page是要釋放緩衝區的線性首地址所在的頁框號 free_pages(page,order) //這個函式類似於__free_pages(page,order),但是它接收的引數為要釋放的第一個頁框的線性地址addr |
DMA驅動主要資料結構:
1)DMA單個核心緩衝區資料結構:
1 2 3 4 5 6 7 8 |
typedef struct dma_buf_s { int size; /* buffer size:緩衝大小 */ dma_addr_t dma_start; /* starting DMA address :緩衝區起始實體地址*/ int ref; /* number of DMA references 緩衝區起始虛擬地址*/ 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 指向下一個緩衝區結構*/ } dma_buf_t; |
2)DMA暫存器資料結構:
1 2 3 4 5 6 7 8 9 10 11 12 |
/* 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;//狀態暫存器 volatile u_long DCSRC;//當前源 volatile u_long DCDST;//當前目的 volatile u_long DMASKTRIG;//觸發掩碼暫存器 } dma_regs_t; |
3)DMA裝置資料結構
1 2 3 4 5 6 7 8 9 |
/* 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通道資料結構
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
/* DMA channel structure */ typedef struct { dmach_t channel;//通道號:可為0,1,2,3 unsigned int in_use; /* Device is allocated 裝置是否已*/ const char *device_id; /* Device name 裝置名*/ dma_buf_t *head; /* where to insert buffers 該DMA通道緩衝區連結串列頭*/ dma_buf_t *tail; /* where to remove buffers該DMA通道緩衝區連結串列尾*/ dma_buf_t *curr; /* buffer currently DMA'ed該DMA通道緩衝區連結串列中的當前緩衝區*/ unsigned long queue_count; /* number of buffers in the queue 連結串列中緩衝區個數*/ 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 //執行讀操作的DMA裝置*/ dma_device_t read; /* to read 執行寫操作的DMA裝置*/ } s3c2410_dma_t; |
DMA驅動主要函式功能分析:
寫一個DMA驅動的主要工作包括:DMA通道申請、DMA中斷申請、控制暫存器設定、掛入DMA等待佇列、清除DMA中斷、釋放DMA通道.
1 2 |
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
1 2 |
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;否則,返回錯誤號
1 |
int s3c2410_dma_stop(dmach_t channel) |
函式描述:停止DMA操作。
1 |
int s3c2410_dma_flush_all(dmach_t channel) |
函式描述:釋放DMA通道所申請的所有記憶體資源
1 |
void s3c2410_free_dma(dmach_t channel) |
函式描述:釋放DMA通道
因為各函式功能強大,一個完整的DMA驅動程式中一般只需呼叫以上3個函式即可。可在驅動初始化中呼叫s3c2410_request_dma,開始DMA傳輸前呼叫s3c2410_dma_queue_buffer,釋放驅動模組時呼叫s3c2410_free_dma。
打賞支援我寫出更多好文章,謝謝!
打賞作者
打賞支援我寫出更多好文章,謝謝!
任選一種支付方式