一個(gè)DMA映射是要分配的DMA緩沖區(qū)與為該緩沖區(qū)生成的、設(shè)備可訪問地址的組合。
DMA映射建立了一個(gè)新的結(jié)構(gòu)類型---dma_addr_t來表示總線地址。
dma_addr_t類型的變量對驅(qū)動(dòng)程序是不透明的,
唯一允許的操作是將它們傳遞給DMA支持例程以及設(shè)備本身。
根據(jù)DMA緩沖區(qū)期望保留的時(shí)間長短,PCI代碼有兩種DMA映射:
1)一致性映射
2)流式DMA映射(推薦)
----------------------------
建立一致性DMA映射
void *dma_alloc_coherent(struct device *dev,size_t size,
dma_addr_t *dma_handle,int flag);
該函數(shù)處理了緩沖區(qū)的分配和映射。
前兩個(gè)參數(shù)是device結(jié)構(gòu)和所需緩沖區(qū)的大小。
函數(shù)在兩處返回結(jié)果:
1)函數(shù)的返回值時(shí)緩沖區(qū)的內(nèi)核虛擬地址,可以被驅(qū)動(dòng)程序使用。
2)相關(guān)的總線地址則保存在dma_handle中。
向系統(tǒng)返回緩沖區(qū)
void dma_free_coherent(struct device *dev,size_t size,
void *vaddr,dma_addr_t dma_handle);
---------
DMA池
DMA池是一個(gè)生成小型、一致性DMA映射的機(jī)制。
調(diào)用dma_alloc_coherent函數(shù)獲得的映射,可能其最小大小為單個(gè)頁。
如果設(shè)備需要的DMA區(qū)域比這還小,就要用DMA池了。
<linux/dmapool.h>
struct dma_pool *dma_pool_create(const char *name,struct device *dev,
size_t size,size_t align,
size_t allocation);
name是DMA池的名字,dev是device結(jié)構(gòu),size是從該池中分配的緩沖區(qū)大小,
align是該池分配操作所必須遵守的硬件對齊原則。
銷毀DMA池
void dma_pool_destroy(struct dma_pool *pool);
DMA池分配內(nèi)存
void *dma_pool_alloc(struct dma_pool *pool,int mem_flags,
dma_addr_t *handle);
釋放內(nèi)存
void dma_pool_free(struct dma_pool *pool,void *vaddr,dma_addr_t addr);
---------------------------
建立流式DMA映射
流式映射具有比一致性映射更為復(fù)雜的接口。
這些映射希望能與已經(jīng)由驅(qū)動(dòng)程序分配的緩沖區(qū)協(xié)同工作,
因而不得不處理那些不是它們選擇的地址。
當(dāng)建立流式映射時(shí),必須告訴內(nèi)核數(shù)據(jù)流動(dòng)的方向。
枚舉類型dma_data_direction:
DMA_TO_DEVICE 數(shù)據(jù)發(fā)送到設(shè)備(如write系統(tǒng)調(diào)用)
DMA_FROM_DEVICE 數(shù)據(jù)被發(fā)送到CPU
DMA_BIDIRECTIONAL 數(shù)據(jù)可雙向移動(dòng)
DMA_NONE 出于調(diào)試目的。
當(dāng)只有一個(gè)緩沖區(qū)要被傳輸?shù)臅r(shí)候,使用dma_map_single函數(shù)映射它:
dma_addr_t dma_map_single(struct device *dev,void *buffer,size_t size,
enum dma_data_direction direction);
返回值是總線地址,可以把它傳遞給設(shè)備。
當(dāng)傳輸完畢后,使用dma_unmap_single函數(shù)刪除映射:
void dma_unmap_single(struct device *dev,dma_addr_t dma_addr,size_t size,
enum dma_data_direction direction);
流式DMA映射的幾條原則:
*緩沖區(qū)只能用于這樣的傳送,即其傳送方向匹配于映射時(shí)給定的方向。
*一旦緩沖區(qū)被映射,它將屬于設(shè)備,而不是處理器。
直到緩沖區(qū)被撤銷映射前,驅(qū)動(dòng)程序不能以任何方式訪問其中的內(nèi)容。
*在DMA處于活動(dòng)期間內(nèi),不能撤銷對緩沖區(qū)映射,否則會(huì)嚴(yán)重破壞系統(tǒng)的穩(wěn)定性。
驅(qū)動(dòng)程序需要不經(jīng)過撤銷映射就訪問流式DMA緩沖區(qū)的內(nèi)容,有如下調(diào)用:
void dma_sync_single_for_cpu(struct device *dev,dma_handle_t bus_addr,
size_t size,enum dma_data_direction direction);
將緩沖區(qū)所有權(quán)交還給設(shè)備:
void dma_sync_single_for_device(struct device *dev,dma_handle_t bus_addr,
size_t size,enum dma_data_direction direction);
--------------------------
單頁流式映射
有時(shí)候,要為page結(jié)構(gòu)指針指向的緩沖區(qū)建立映射,比如
為get_user_pages獲得的用戶空間緩沖區(qū)。
dma_addr_t dma_map_page(struct device *dev,struct page *page,
unsigned long offset,size_t size,
enum dma_data_direction direction);
void dma_unmap_page(struct device *dev,dma_addr_t dma_address,
size_t size,enum dma_data_direction direction);
---------------------------
分散/聚集映射
有幾個(gè)緩沖區(qū),它們需要與設(shè)備雙向傳輸數(shù)據(jù)。
可以簡單地依次映射每一個(gè)緩沖區(qū)并且執(zhí)行請求的操作,
但是一次映射整個(gè)緩沖區(qū)表還是很有利的。
映射分散表的第一步是建立并填充一個(gè)描述被傳輸緩沖區(qū)的
scatterlist結(jié)構(gòu)的數(shù)組。
<linux/scatterlist.h>
scatterlist結(jié)構(gòu)的成員:
struct page *page;
unsigned int length;
unsigned int offset;
映射
int dma_map_sg(struct device *dev,struct scatterlist *sg,int nents,
enum dma_data_direction direction);
解除
void dma_unmap_sg(struct device *dev,struct scatterlsit *list,
int nents,enum dma_data_direction direction);
--------------------------
PCI雙重地址周期映射
通用DMA支持層使用32位總線地址,然而PCI總線還支持64位地址模式,即
雙重地址周期(DAC)。<linux/pci.h>
通用DMA層并不支持該模式。
要使用PCI總線的DAC,必須設(shè)置一個(gè)單獨(dú)的DMA掩碼:
int pci_dac_set_dma_mask(struct pci_dev *pdev,u64 mask);
建立映射
dma64_addr_t pci_dac_page_to_dma(struct pci_dev *pdev,struct page *page,
unsigned long offset,int direction);
-----------------------------
一個(gè)簡單的PCI DMA例子
這里提供了一個(gè)PCI設(shè)備的DMA例子dad(DMA Acquisition Device)的一部分,說明如何使用DMA映射:
int dad_transfer(struct dad_dev *dev,int write,void *buffer,size_t count)
{
dma_addr_t bus_addr;
dev->dma_dir = (write ? DMA_TO_DEVICE : DMA_FROM_DEVICE);
dev->dma_size = count;
/*映射DMA需要的緩沖區(qū)*/
bus_addr = dma_map_single(&dev->pci_dev->dev,buffer,count,dev->dma_dir);
writeb(dev->registers.command,DAD_CMD_DISABLEDMA);
writeb(dev->registers.command,write ? DAD_CMD_WR : DAD_CMD_RD);
writeb(dev->registers.addr,cpu_to_le32(bus_addr)); /*設(shè)置*/
writeb(dev->registers.len,cpu_to_le32(count));
/*開始操作*/
writeb(dev->registers.command,DAD_CMD_ENABLEDMA);
return 0;
}
該函數(shù)映射了準(zhǔn)備進(jìn)行傳輸?shù)木彌_區(qū)并且啟動(dòng)設(shè)備操作。
另一半工作必須在中斷服務(wù)例程中完成,如:
void dad_interrupt(int irq,void *dev_id,struct pt_regs *regs)
{
struct dad_dev *dev = (struct dad_dev *)dev_id;
/* 確定中斷是由對應(yīng)的設(shè)備發(fā)來的*/
dma_unmap_single(dev->pci_dev->dev,dev->dma_addr,
dev->dma_size,dev->dma_dir);
/* 釋放之后,才能訪問緩沖區(qū),把它拷貝給用戶 */
...
}
-------------------------------------------------------------------
ISA設(shè)備的DMA
ISA總線允許兩種DMA傳輸:本地DMA和ISA總線控制DMA。
只討論本地(native)DMA。***********************非常重要!!!!!
本地DMA使用主板上的標(biāo)準(zhǔn)DMA控制器電路來驅(qū)動(dòng)ISA總線上的信號線。
本地DMA,要關(guān)注三種實(shí)體:
*8237 DMA控制器(DMAC)
控制器保存了有關(guān)DMA傳輸?shù)男畔ⅲ绶较?、?nèi)存地址、傳輸數(shù)據(jù)量大小等。
還包含了一個(gè)跟蹤傳送狀態(tài)的計(jì)數(shù)器。
當(dāng)控制器接收到一個(gè)DMA請求信號時(shí),它將獲得總線控制權(quán)并驅(qū)動(dòng)信號線,
這樣設(shè)備就能讀寫數(shù)據(jù)了。
*外圍設(shè)備
當(dāng)設(shè)備準(zhǔn)備傳送數(shù)據(jù)時(shí),必須激活DMA請求信號。
DMAC負(fù)責(zé)管理實(shí)際的傳輸工作;當(dāng)控制器選通設(shè)備后,
硬件設(shè)備就可以順序地讀/寫總線上的數(shù)據(jù)。
當(dāng)傳輸結(jié)束時(shí),設(shè)備通常會(huì)產(chǎn)生一個(gè)中斷。
*設(shè)備驅(qū)動(dòng)程序
設(shè)備驅(qū)動(dòng)程序完成的工作很少,
它只是負(fù)責(zé)提供DMA控制器的方向、總線地址、傳輸量的大小等。
它還與外圍設(shè)備通信,做好傳輸數(shù)據(jù)的準(zhǔn)備,當(dāng)DMA傳輸完畢后,響應(yīng)中斷。
在PC中使用的早期DMA控制器能夠管理四個(gè)“通道”,
每個(gè)通道都與一套DMA寄存器相關(guān)聯(lián)。
DMA控制器是系統(tǒng)資源,因此,內(nèi)核協(xié)助處理這一資源。
內(nèi)核使用DMA注冊表為DMA通道提供了請求/釋放機(jī)制,
并且提供了一組函數(shù)在DMA控制器中配置通道信息。