Linux 下的DMA浅析
转载于:http://www.codexiu.cn/linux/blog/23237/
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中):
[cpp] view
plain copy
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);
posted on 2016-11-07 22:47 Red_Point 阅读(4794) 评论(0) 编辑 收藏 举报
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】凌霞软件回馈社区,博客园 & 1Panel & Halo 联合会员上线
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】博客园社区专享云产品让利特惠,阿里云新客6.5折上折
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步