歡迎來到Linux教程網
Linux教程網
Linux教程網
Linux教程網
Linux教程網 >> Linux基礎 >> 關於Linux >> Linux 下的DMA淺析

Linux 下的DMA淺析

日期:2017/3/1 12:12:59   编辑:關於Linux

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中):

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);
			return ret;
		}
		if (mem->flags & DMA_MEMORY_EXCLUSIVE)
			return NULL;
	}
 
  //不是設備的DMA映射
	if (dev == NULL || (dev->coherent_dma_mask < 0xffffffff))
		gfp |= GFP_DMA;
  //分配空閒頁
	ret = (void *)__get_free_pages(gfp, order);
 
	if (ret != NULL) {
		memset(ret, 0, size);//清0
		*dma_handle = virt_to_phys(ret);//得到物理地址
	}
	return ret;
}

當不再需要緩沖區時(通常在模塊卸載時),應該調用函數 pci_free_consitent 將它返還給系統。


2、建立流式 DMA 映射

在流式 DMA 映射的操作中,緩沖區傳送方向應匹配於映射時給定的方向值。緩沖區被映射後,它就屬於設備而不再屬於處理器了。在緩沖區調用函數pci_unmap_single撤銷映射之前,驅動程序不應該觸及其內容。

在緩沖區為 DMA 映射時,內核必須確保緩沖區中所有的數據已經被實際寫到內存。可能有些數據還會保留在處理器的高速緩沖存儲器中,因此必須顯式刷新。在刷新之後,由處理器寫入緩沖區的數據對設備來說也許是不可見的。

如果欲映射的緩沖區位於設備不能訪問的內存區段時,某些體系結構僅僅會操作失敗,而其它的體系結構會創建一個反彈緩沖區。反彈緩沖區是被設備訪問的獨立內存區域,反彈緩沖區復制原始緩沖區的內容。

函數pci_map_single映射單個用於傳送的緩沖區,返回值是可以傳遞給設備的總線地址,如果出錯的話就為 NULL。一旦傳送完成,應該使用函數pci_unmap_single 刪除映射。其中,參數direction為傳輸的方向,取值如下:

PCI_DMA_TODEVICE 數據被發送到設備。
PCI_DMA_FROMDEVICE如果數據將發送到 CPU。
PCI_DMA_BIDIRECTIONAL數據進行兩個方向的移動。
PCI_DMA_NONE 這個符號只是為幫助調試而提供。

函數pci_map_single分析如下(在arch/i386/kernel/pci-dma.c中)

static inline dma_addr_t pci_map_single(struct pci_dev *hwdev, 
                void *ptr, size_t size, int direction)
{
	return dma_map_single(hwdev == NULL ? NULL : &hwdev->dev, ptr, size, 
                                      (enum ma_data_direction)direction);
}

函數dma_map_single映射一塊處理器虛擬內存,這塊虛擬內存能被設備訪問,返回內存的物理地址,函數dma_map_single分析如下(在include/asm-i386/dma-mapping.h中):

static inline dma_addr_t dma_map_single(struct device *dev, void *ptr,
                        size_t size, enum dma_data_direction direction)

{
	BUG_ON(direction == DMA_NONE);
  //可能有些數據還會保留在處理器的高速緩沖存儲器中,因此必須顯式刷新
	flush_write_buffers();
	return virt_to_phys(ptr); //虛擬地址轉化為物理地址

}

3、分散/集中映射

分散/集中映射是流式 DMA 映射的一個特例。它將幾個緩沖區集中到一起進行一次映射,並在一個 DMA 操作中傳送所有數據。這些分散的緩沖區由分散表結構scatterlist來描述,多個分散的緩沖區的分散表結構組成緩沖區的struct scatterlist數組

分散表結構列出如下(在include/asm-i386/scatterlist.h):

struct scatterlist {
    struct page		*page;
    unsigned int	offset;
    dma_addr_t		dma_address;  //用在分散/集中操作中的緩沖區地址
    unsigned int	length;//該緩沖區的長度
};

每一個緩沖區的地址和長度會被存儲在 struct scatterlist 項中,但在不同的體系結構中它們在結構中的位置是不同的。下面的兩個宏定義來解決平台移植性問題,這些宏定義應該在一個pci_map_sg 被調用後使用:

//從該分散表項中返回總線地址
#define sg_dma_address(sg)	?sg)->dma_address)
//返回該緩沖區的長度 
#define sg_dma_len(sg)		?sg)->length)
函數pci_map_sg完成分散/集中映射,其返回值是要傳送的 DMA 緩沖區數;它可能會小於 nents(也就是傳入的分散表項的數量),因為可能有的緩沖區地址上是相鄰的。一旦傳輸完成,分散/集中映射通過調用函數pci_unmap_sg 來撤銷映射。 函數pci_map_sg分析如下(在include/asm-generic/pci-dma-compat.h中):
static inline int pci_map_sg(struct pci_dev *hwdev, struct scatterlist *sg,
	                            int nents, int direction)
{
	return dma_map_sg(hwdev == NULL ? NULL : &hwdev->dev, sg, nents, 
(enum dma_data_direction)direction);
}
include/asm-i386/dma-mapping.h
static inline int dma_map_sg(struct device *dev, struct scatterlist *sg, 
int nents, enum dma_data_direction direction)
{
	int i;
 
	BUG_ON(direction == DMA_NONE);
 
	for (i = 0; i < nents; i++ ) {
		BUG_ON(!sg[i].page);
    //將頁及頁偏移地址轉化為物理地址
		sg[i].dma_address = page_to_phys(sg[i].page) + sg[i].offset;
	}
    //可能有些數據還會保留在處理器的高速緩沖存儲器中,因此必須顯式刷新
	flush_write_buffers();
	return nents;
}

五、DMA池

許多驅動程序需要又多又小的一致映射內存區域給DMA描述子或I/O緩存buffer,這使用DMA池比用dma_alloc_coherent分配的一頁或多頁內存區域好,DMA池用函數dma_pool_create創建,用函數dma_pool_alloc從DMA池中分配一塊一致內存,用函數dmp_pool_free放內存回到DMA池中,使用函數dma_pool_destory釋放DMA池的資源。

結構dma_pool是DMA池描述結構,列出如下:

struct dma_pool {	/* the pool */
	struct list_head	page_list;//頁鏈表
	spinlock_t		lock;
	size_t			blocks_per_page; //每頁的塊數
	size_t			size;     //DMA池裡的一致內存塊的大小
	struct device		*dev; //將做DMA的設備
	size_t			allocation; //分配的沒有跨越邊界的塊數,是size的整數倍
	char			name [32]; //池的名字
	wait_queue_head_t	waitq;  //等待隊列
	struct list_head	pools;
};

函數dma_pool_create給DMA創建一個一致內存塊池,其參數name是DMA池的名字,用於診斷用,參數dev是將做DMA的設備,參數size是DMA池裡的塊的大小,參數align是塊的對齊要求,是2的冪,參數allocation返回沒有跨越邊界的塊數(或0)。

函數dma_pool_create返回創建的帶有要求字符串的DMA池,若創建失敗返回null。對被給的DMA池,函數dma_pool_alloc被用來分配內存,這些內存都是一致DMA映射,可被設備訪問,且沒有使用緩存刷新機制,因為對齊原因,分配的塊的實際尺寸比請求的大。如果分配非0的內存,從函數dma_pool_alloc返回的對象將不跨越size邊界(如不跨越4K字節邊界)。這對在個體的DMA傳輸上有地址限制的設備來說是有利的。

  函數dma_pool_create分析如下(在drivers/base/dmapool.c中):

struct dma_pool *dma_pool_create (const char *name, struct device *dev,
	          size_t size, size_t align, size_t allocation)
{
	struct dma_pool		*retval;
 
	if (align == 0)
		align = 1;
	if (size == 0)
		return NULL;
	else if (size < align)
		size = align;
	else if ((size % align) != 0) {//對齊處理
		size += align + 1;
		size &= ~(align - 1);
	}
  //如果一致內存塊比頁大,是分配為一致內存塊大小,否則,分配為頁大小
	if (allocation == 0) {
		if (PAGE_SIZE < size)//頁比一致內存塊小
			allocation = size;
		else
			allocation = PAGE_SIZE;//頁大小
		// FIXME: round up for less fragmentation
	} else if (allocation < size)
		return NULL;
  //分配dma_pool結構對象空間
	if (!(retval = kmalloc (sizeof *retval, SLAB_KERNEL)))
		return retval;
 
	strlcpy (retval->name, name, sizeof retval->name);
 
	retval->dev = dev;
  //初始化dma_pool結構對象retval
	INIT_LIST_HEAD (&retval->page_list);//初始化頁鏈表
	spin_lock_init (&retval->lock);
	retval->size = size;
	retval->allocation = allocation;
	retval->blocks_per_page = allocation / size;
	init_waitqueue_head (&retval->waitq);//初始化等待隊列
 
	if (dev) {//設備存在時
		down (&pools_lock);
		if (list_empty (&dev->dma_pools))
      //給設備創建sysfs文件系統屬性文件
			device_create_file (dev, &dev_attr_pools);
		/* note:  not currently insisting "name" be unique */
		list_add (&retval->pools, &dev->dma_pools); //將DMA池加到dev中
		up (&pools_lock);
	} else
		INIT_LIST_HEAD (&retval->pools);
 
	return retval;
}

函數dma_pool_alloc從DMA池中分配一塊一致內存,其參數pool是將產生塊的DMA池,參數mem_flags是GFP_*位掩碼,參數handle是指向塊的DMA地址,函數dma_pool_alloc返回當前沒用的塊的內核虛擬地址,並通過handle給出它的DMA地址,如果內存塊不能被分配,返回null。

函數dma_pool_alloc包裹了dma_alloc_coherent頁分配器,這樣小塊更容易被總線的主控制器使用。這可能共享slab分配器的內容。

函數dma_pool_alloc分析如下(在drivers/base/dmapool.c中):

void *dma_pool_alloc (struct dma_pool *pool, int mem_flags, dma_addr_t *handle)
{
	unsigned long		flags;
	struct dma_page		*page;
	int			map, block;
	size_t			offset;
	void			*retval;
 
restart:
	spin_lock_irqsave (&pool->lock, flags);
	list_for_each_entry(page, &pool->page_list, page_list) {
		int		i;
		/* only cachable accesses here ... */
        //遍歷一頁的每塊,而每塊又以32字節遞增
		for (map = 0, i = 0;
				i < pool->blocks_per_page; //每頁的塊數
				i += BITS_PER_LONG, map++) { // BITS_PER_LONG定義為32
			if (page->bitmap [map] == 0)
				continue;
			block = ffz (~ page->bitmap [map]);//找出第一個0
			if ((i + block) < pool->blocks_per_page) {
				clear_bit (block, &page->bitmap [map]);
       //得到相對於頁邊界的偏移
				offset = (BITS_PER_LONG * map) + block;
				offset *= pool->size;
				goto ready;
			}
		}
	}
//給DMA池分配dma_page結構空間,加入到pool->page_list鏈表,
//並作DMA一致映射,它包括分配給DMA池一頁。
// SLAB_ATOMIC表示調用 kmalloc(GFP_ATOMIC) 直到失敗為止,
//然後它等待內核釋放若干頁面,接下來再一次進行分配。
	if (!(page = pool_alloc_page (pool, SLAB_ATOMIC))) {
		if (mem_flags & __GFP_WAIT) {
			DECLARE_WAITQUEUE (wait, current);
 
			current->state = TASK_INTERRUPTIBLE;
			add_wait_queue (&pool->waitq, &wait);
			spin_unlock_irqrestore (&pool->lock, flags);
 
			schedule_timeout (POOL_TIMEOUT_JIFFIES);
 
			remove_wait_queue (&pool->waitq, &wait);
			goto restart;
		}
		retval = NULL;
		goto done;
	}
 
	clear_bit (0, &page->bitmap [0]);
	offset = 0;
ready:
	page->in_use++;
	retval = offset + page->vaddr; //返回虛擬地址
	*handle = offset + page->dma; //相對DMA地址
#ifdef	CONFIG_DEBUG_SLAB
	memset (retval, POOL_POISON_ALLOCATED, pool->size);
#endif
done:
	spin_unlock_irqrestore (&pool->lock, flags);
	return retval;
}

六、一個簡單的使用DMA 例子

示例:下面是一個簡單的使用DMA進行傳輸的驅動程序,它是一個假想的設備,只列出DMA相關的部分來說明驅動程序中如何使用DMA的。

函數dad_transfer是設置DMA對內存buffer的傳輸操作函數,它使用流式映射將buffer的虛擬地址轉換到物理地址,設置好DMA控制器,然後開始傳輸數據。

int dad_transfer(struct dad_dev *dev, int write, void *buffer, 
                size_t count) 
{ 
   dma_addr_t bus_addr; 
   unsigned long flags; 
 
   /* Map the buffer for DMA */ 
   dev->dma_dir = (write ? PCI_DMA_TODEVICE : PCI_DMA_FROMDEVICE); 
   dev->dma_size = count;
  //流式映射,將buffer的虛擬地址轉化成物理地址
   bus_addr = pci_map_single(dev->pci_dev, buffer, count, 
                             dev->dma_dir); 
   dev->dma_addr = bus_addr; //DMA傳送的buffer物理地址
 
   //將操作控制寫入到DMA控制器寄存器,從而建立起設備 
   writeb(dev->registers.command, DAD_CMD_DISABLEDMA); 
 //設置傳輸方向--讀還是寫
   writeb(dev->registers.command, write ? DAD_CMD_WR : DAD_CMD_RD);  
   writel(dev->registers.addr, cpu_to_le32(bus_addr));//buffer物理地址 
   writel(dev->registers.len, cpu_to_le32(count)); //傳輸的字節數
 
   //開始激活DMA進行數據傳輸操作 
   writeb(dev->registers.command, DAD_CMD_ENABLEDMA); 
   return 0; 
}

函數dad_interrupt是中斷處理函數,當DMA傳輸完時,調用這個中斷函數來取消buffer上的DMA映射,從而讓內核程序可以訪問這個buffer。

void dad_interrupt(int irq, void *dev_id, struct pt_regs *regs) 
{ 
  struct dad_dev *dev = (struct dad_dev *) dev_id; 

  /* Make sure it's really our device interrupting */  

  /* Unmap the DMA buffer */ 
  pci_unmap_single(dev->pci_dev, dev->dma_addr, dev->dma_size, 
       dev->dma_dir); 

  /* Only now is it safe to access the buffer, copy to user, etc. */ 
  ... 
}
函數dad_open打開設備,此時應申請中斷號及DMA通道
int dad_open (struct inode *inode, struct file *filp) 
{ 
  struct dad_device *my_device; 

  // SA_INTERRUPT表示快速中斷處理且不支持共享 IRQ 信號線
  if ( (error = request_irq(my_device.irq, dad_interrupt, 
                            SA_INTERRUPT, "dad", NULL)) ) 
      return error; /* or implement blocking open */ 

  if ( (error = request_dma(my_device.dma, "dad")) ) { 
      free_irq(my_device.irq, NULL); 
      return error; /* or implement blocking open */ 
  } 

  return 0; 
}

在與open 相對應的 close 函數中應該釋放DMA及中斷號。

void dad_close (struct inode *inode, struct file *filp) 
{ 
  struct dad_device *my_device; 
  free_dma(my_device.dma); 
  free_irq(my_device.irq, NULL); 
  ……
}
函數dad_dma_prepare初始化DMA控制器,設置DMA控制器的寄存器的值,為 DMA 傳輸作准備。
int dad_dma_prepare(int channel, int mode, unsigned int buf, 
                  unsigned int count) 
{ 
  unsigned long flags; 

  flags = claim_dma_lock(); 
  disable_dma(channel); 
  clear_dma_ff(channel); 
  set_dma_mode(channel, mode); 
  set_dma_addr(channel, virt_to_bus(buf)); 
  set_dma_count(channel, count); 
  enable_dma(channel); 
  release_dma_lock(flags); 

  return 0; 
}

函數dad_dma_isdone用來檢查 DMA 傳輸是否成功結束。

int dad_dma_isdone(int channel) 
{ 
   int residue; 
   unsigned long flags = claim_dma_lock (); 
   residue = get_dma_residue(channel); 
   release_dma_lock(flags); 
   return (residue == 0); 
}
Copyright © Linux教程網 All Rights Reserved