Linux核心開發之記憶體與I/O訪問(六)

來源:互聯網
上載者:User

“小王,再告訴你一個好訊息,今天是咱們裝置驅動程式核心基礎理論的最後一節課了,戰鬥就已經到了最後一刻了,開心不”我眉飛色舞的對小王說。

“嗯,開心,我掙紮許久了,終於結束了,只是..”小王傷感的說“只是我覺得怎麼能一下就沒了呢, 心裡空蕩蕩的”.

“沒關係的…”看著小王噘著嘴調皮而又可愛的樣子,我也心軟了”核心的理論是講完了,但你不是沒動過手嗎,還有很多路要走呢..我還舍…”我一把蒙住自己的嘴.

  嘿嘿,心裡咋想咋們都明白,是不…別傷感了,繼續咱們上節的東西:

  上節我們說到了dma_mem_alloc()函數,需要說明的是DMA的硬體使用匯流排地址而非物理地址,匯流排地址是從裝置角度上看到的記憶體位址,物理地址是從CPU角度上看到的未經轉換的記憶體位址(經過轉換的那叫虛擬位址)。在PC上,對於ISA和PCI而言,匯流排即為物理地址,但並非每個平台都是如此。由於有時候介面匯流排是通過橋接電路被串連,橋接電路會將IO地址映射為不同的物理地址。例如,在PRep(PowerPC Reference Platform)系統中,物理地址0在裝置端看起來是0X80000000,而0通常又被映射為虛擬位址0xC0000000,所以同一地址就具備了三重身份:物理地址0,匯流排地址0x80000000及虛擬位址0xC0000000,還有一些系統提供了頁面映射機制,它能將任意的頁面映射為連續的外設匯流排地址。核心提供了如下函數用於進行簡單的虛擬位址/匯流排地址轉換:

unsigned long virt_to_bus(volatile void *address);void *bus_to_virt(unsigned long address);

在使用IOMMU或反彈緩衝區的情況下,上述函數一般不會正常工作。而且,這兩個函數並不建議使用。

需要說明的是裝置不一定能在所有的記憶體位址上執行DMA操作,在這種情況下應該通過下列函數執行DMA位址遮罩:

int dma_set_mask(struct device *dev, u64 mask);

比如,對於只能在24位地址上執行DMA操作的裝置而言,就應該調用dma_set_mask(dev, 0xffffffff).DMA映射包括兩個方面的工作:分配一片DMA緩衝區;為這片緩衝區產生裝置可訪問的地址。結合前面所講的,DMA映射必須考慮Cache一致性問題。核心中提供了一下函數用於分配一個DMA一致性的記憶體地區:

void *dma_alloc_coherent(struct device *dev, size_t size, dma_addr_t *handle, gfp_t gfp);

這個函數的傳回值為申請到的DMA緩衝區的虛擬位址。此外,該函數還通過參數handle返回DMA緩衝區的匯流排地址。與之對應的釋放函數為:

void dma_free_coherent(struct device *dev, size_t size, void *cpu_addr, dma_addr_t handle);

以下函數用於分配一個寫合并(writecombinbing)的DMA緩衝區:

void *dma_alloc_writecombine(struct device *dev, size_t size, dma_addr_t *handle, gfp_t gfp);

與之對應的是釋放函數:dma_free_writecombine(),它其實就是dma_free_conherent,只不過是用了#define重新命名而已。

此外,Linux核心還提供了PCI裝置申請DMA緩衝區的函數pci_alloc_consistent(),原型為:

void *pci_alloc_consistent(struct pci_dev *dev, size_t size, dma_addr_t *dma_addrp);  對應的釋放函數為:
void pci_free_consistent(struct pci_dev *pdev, size_t size, void *cpu_addr, dma_addr_t dma_addr);
相對於一致性DMA映射而言,流式DMA映射的介面較為複雜。對於單個已經分配的緩衝區而言,使用dma_map_single()可實現流式DMA映射:
dma_addr_t dma_map_single(struct device *dev, void *buffer, size_t size, enum dma_data_direction direction);  如果映射成功,返回的是匯流排地址,否則返回NULL.最後一個參數DMA的方向,可能取DMA_TO_DEVICE, DMA_FORM_DEVICE, DMA_BIDIRECTIONAL和DMA_NONE;
與之對應的反函數是:
void dma_unmap_single(struct device *dev,dma_addr_t *dma_addrp,size_t size,enum dma_data_direction direction);
通常情況下,裝置驅動不應該訪問unmap()的流式DMA緩衝區,如果你說我就願意這麼做,我又說寫什麼呢,選擇了權利,就選擇了責任,對吧。這時可先使用如下函數獲得DMA緩衝區的擁有權:
void dma_sync_single_for_cpu(struct device *dev,dma_handle_t bus_addr, size_t size, enum dma_data_direction direction);
在驅動訪問完DMA緩衝區後,應該將其所有權還給裝置,通過下面的函數:
void dma_sync_single_for_device(struct device *dev,dma_handle_t bus_addr, size_t size, enum dma_data_direction direction);如果裝置要求較大的DMA緩衝區,在其支援SG模式的情況下,申請多個不連續的,相對較小的DMA緩衝區通常是防止申請太大的連續物理空間的方法,在Linux核心中,使用如下函數映射SG:
int dma_map_sg(struct device *dev,struct scatterlist *sg, int nents,enum dma_data_direction direction); 其中nents是散列表入口的數量,該函數的傳回值是DMA緩衝區的數量,可能小於nents。對於scatterlist中的每個項目,dma_map_sg()為裝置產生恰當的匯流排地址,它會合并物理上臨近的記憶體地區。下面在給出scatterlist結構:
struct scatterlist{   struct page *page;       unsigned int offset;   //位移量   dma_addr_t dma_address;   //匯流排地址      unsigned int length;   //緩衝區長度}
執行dma_map_sg()後,通過sg_dma_address()後可返回scatterlist對應緩衝區的匯流排結構,sg_dma_len()可返回scatterlist對應的緩衝區的長度,這兩個函數的原型是:
dma_addr_t sg_dma_address(struct scatterlist *sg);       unsigned int sg_dma_len(struct scatterlist *sg);
在DMA傳輸結束後,可通過dma_map_sg()的反函數dma_unmap_sg()去除DMA映射:void dma_map_sg(struct device *dev, struct scatterlist *sg, int nents, enum dma_data_direction direction);   SG映射屬於流式DMA映射,與單一緩衝區情況下流式DMA映射類似,如果裝置驅動一定要訪問映射情況下的SG緩衝區,應該先調用如下函數:int dma_sync_sg_for_cpu(struct device *dev,struct scatterlist *sg, int nents,enum dma_data_direction direction); 訪問完後,通過下列函數將所有權返回給裝置:int dma_map_device(struct device *dev,struct scatterlist *sg, int nents,enum dma_data_direction direction);
Linux系統中可以有一個相對簡單的方法預先分配緩衝區,那就是同步“mem=”參數預留記憶體。例如,對於記憶體為64MB的系統,通過給其傳遞mem=62MB命令列參數可以使得頂部的2MB記憶體被預留出來作為IO記憶體使用量,這2MB記憶體可以被靜態映射,也可以執行ioremap().相應的函數都介紹完了:說真的,好費勁啊,我都想放棄了,可為了小王,我繼續哈..在linux裝置驅動中如何操作呢:像使用中斷一樣,在使用DMA之前,裝置驅動程式需要首先向系統申請DMA通道,申請DMA通道的函數如下:int request_dma(unsigned int dmanr, const char * device_id);  同樣的,裝置結構體指標可作為傳入device_id的最佳參數。使用完DMA通道後,應該使用如下函數釋放該通道:void free_dma(unsinged int dmanr);
作為本篇的最後,也作為Linux裝置驅動核心理論的結束篇,小王,我給你總結一下在Linux裝置驅動中DMA相關代碼的流程。如下所示:
“小王,我要講的講完了,我輕鬆了,你可要忙碌了,把這一個月來的知識點都好好串串,為了你,我隨叫隨到..”我說“課餘,我也準備一些後面實際的操作內容,幫你串串”我發現自己越來越喜歡小王了..
"嗯,我會的,放心吧.."那美麗的而迷人的微笑眼神又出現了..
 
 
 

相關文章

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.