DMA(direct memory access):直接存储器存取,通俗地说就是数据的搬运工,它的存在使得CPU可以脱离繁琐的数据搬运,可以腾出更多时间去做更有意义的事,早在学生时代,学习STM32的时候,就已经接触和使用了DMA.因为STM32上就有DMA,可以看到DMA是已经很普及的器件了,在display, uart, sound等等诸多方面都可以看到DMA的身影.所以,很有必要掌握DMA的使用.
本次以三星Exynos4412为平台,进行DMA驱动编程来介绍,在Exynos4412上使用的DMA器件是PL330,下面是三星的datasheet:

可以看到,猎户4上有3个DMA控制器,其中DMA是支持内存到内存的DMA控制器,DMA0和DMA1是同时支持外设到内存&内存到外设的DMA控制器,因为在Linux里已经把DMA作为一个独立的子系统(可见其地位),有专门的编程接口,所以,这里我们就不再去专门了解相关的寄存器了,我们直接调用相关接口进行驱动编程就可以了,另外,三星的datasheet上也没有关于PL330相关寄存器的详细介绍,需要去下载PL330的datasheet,我是在ARM的网站上下载的,回头你们也可以下载细细研究寄存器,稍后我会给出其他博文也是有对寄存器进行介绍的.
下面几篇博文写的不错,有些内容是从这些博文里面摘抄下来的:
https://blog.csdn.net/u013625961/article/details/68945333
https://blog.csdn.net/automan12138/article/details/75201161
https://blog.csdn.net/fivedoumi/article/details/50237187
https://www.cnblogs.com/lifexy/p/7880737.html
关于DMA硬件原理就不说了,自行在网上找资料学一下.
DMA种类:
分为外设DMA和DMA控制器。其中外设DMA实现的为特定的外设与内存之间的数据传输,一般是外设向RAM单向传输数据。而DMA控制器则可以实现任意外设与内存之间的数据传输。此时外设跟CPU控制器之间通过流控制信号来保证传输通道的正常运行。
 
DMA传输的数据宽度不固定.

还可以实现任意长度的burst 操作。burst是DMA控制地址总线自行改变。
DMA也支持分散集合模式,即内存中数据并非连续,而是分配在多个块中,块大小也不一样,这时候DMA可以根据Scatter Gather Descriptors来进行DMA数据传输。

Descriptors是一个单向列表,描述了每块数据的位置和大小还有其他配置。DMA自行解析Descriptors的内容进行数据传输并寻找小一个链表节点,
如果Descriptor 链表是一个循环链接,则传输被叫做环形传输(Cyclic Transfers)。

linux实现了DMA框架,叫做DMA Engine,内核驱动开发者必须按照固定的流程编码才能正确的使用DMA。DMA Engine提供出来了Slave API供其它内核调用。这些API实现了复杂的scatter gather 传输模式,通过这些API实现DMA传输的驱动被叫做DMA client.
DMA中调用API的顺序为:

申请DMA通道
struct dma_chan *dma_request_channel(dma_cap_mask_t mask, dma_filter_fn filter_fn, void *filter_param);
其中dma_cap_mase_t是根据dma_cap_sets指定的DMA传输类型;filter_param是外设ID。如:
dma_cap_mask_t mask;
dma_cap_zero(mask);
dma_cap_set(DMA_MEMCPY, mask);
dma_chan1 = dma_request_channel(mask, NULL, NULL);
DMA通道的配置
int dmaengine_slave_config(struct dma_chan *chan, struct dma_slave_config *config);
可以通过config结构体设置DMA通道宽度、数据传输宽带、源地址目的地址等信息。
获得传输描述符 通过device_prep_slave_sg() 或者
device_prep_dma_cyclic() 或者
device_prep_dma_memcpy() 获取desc,再将回调函数指针穿给desc->callback。
提交传输 调用dmaengine_submit((struct dma_async_tx_descriptor *)desc),将desc提交到DMA等待队列。
启动传输 dmaengine_issue_pending调用会从第一个描述符开始进行传输。如果DMA 设备驱动有回调函数的话,会在传输完成后执行。
下面介绍一下获得传输描述符的三种方式。 device_prep_dma_memcpy(),明显是DMA内存到内存的拷贝.
有些DMA支持分散集合模式,即内存中数据并非连续,这中情况可以调用通过device_prep_slave_sg函数进行传输,描述符是一个单向列表,描述了每块数据的位置和大小还有其他配置。DMA自行解析描述符的内容进行数据传输并寻找下一个链表节点。
如果是循环连接,则传输被叫做循环传输,需要用到device_prep_dma_cyclic()函数进行传输,例如linux下的串口驱动,它的传输buffer是一个环形缓冲区,它用DMA传输时就采用了循环传输方式。
下面写一个内存到内存的DMA驱动程序:
#include <linux/init.h>
#include <linux/module.h>
#include <linux/fs.h>
#include <linux/sched.h>
#include <linux/miscdevice.h>
#include <linux/irq.h>
#include <linux/delay.h>
#include <linux/device.h>
#include <linux/string.h>
#include <linux/errno.h>
#include <linux/types.h>
#include <linux/slab.h>
#include <linux/dmaengine.h>
#include <linux/dma-mapping.h>
#include <asm/io.h>
#include <asm/uaccess.h>
#include <linux/amba/pl330.h>
#include <mach/dma.h>
#include <plat/dma-ops.h>
#define BUF_SIZE 512
#define MEM_CPY_NO_DMA _IOW('L', 0x1210, int)
#define MEM_CPY_DMA _IOW('L', 0x1211, int)
char *src = NULL;
char *dst = NULL;
dma_addr_t dma_src;
dma_addr_t dma_dst;
enum dma_ctrl_flags flags;
dma_cookie_t cookie;
static struct dma_chan *chan = NULL;
struct dma_device *dev = NULL;
struct dma_async_tx_descriptor *tx = NULL;
void dma_callback_func(void)
{
if(0 == memcmp(src, dst, BUF_SIZE))
printk("MEM_CPY_DMA OK\n");
else
printk("MEM_CPY_DMA ERROR\n");
}
int
exynos4_dma_open(struct inode *inode, struct file *filp)
{
return 0;
}
long
exynos4_dma_ioctl(struct file *filp, unsigned int cmd, unsigned long arg)
{
int i = 0;
memset(src, 0xAA, BUF_SIZE);
memset(dst, 0xBB, BUF_SIZE);
switch (cmd)
{
case MEM_CPY_NO_DMA:
for(i = 0; i < BUF_SIZE; i++)
dst[i] = src[i];
if(0 == memcmp(src, dst, BUF_SIZE))
printk("MEM_CPY_NO_DMA OK\n");
else
printk("MEM_CPY_DMA ERROR\n");
break;
case MEM_CPY_DMA:
flags = DMA_CTRL_ACK | DMA_PREP_INTERRUPT;
dev = chan->device;
tx = dev->device_prep_dma_memcpy(chan, dma_dst, dma_src, BUF_SIZE, flags);
if(!tx)
printk("%s failed to prepare DMA memcpy\n", __func__);
tx->callback = dma_callback_func; // set call back function
tx->callback_param = NULL;
cookie = tx->tx_submit(tx); // submit the desc
if(dma_submit_error(cookie)) {
printk("failed to do DMA tx_submit");
}
dma_async_issue_pending(chan); // begin dma transfer
break;
default:
break;
}
return 0;
}
int
exynos4_dma_release(struct inode *inode, struct file *filp)
{
return 0;
}
const struct file_operations exynos4_dma_fops = {
.open = exynos4_dma_open,
.unlocked_ioctl = exynos4_dma_ioctl,
.release = exynos4_dma_release,
};
struct miscdevice dma_misc = {
.minor = MISC_DYNAMIC_MINOR,
.name = "dma_m2m_test",
.fops = &exynos4_dma_fops,
};
static void __devexit
exynos4_dma_exit(void)
{
dma_release_channel(chan);
dma_free_coherent(NULL, BUF_SIZE, src, &dma_src);
dma_free_coherent(NULL, BUF_SIZE, dst, &dma_dst);
misc_deregister(&dma_misc);
}
static int __devinit
exynos4_dma_init(void)
{
int ret = misc_register(&dma_misc);
if(ret < 0){
printk("%s misc register failed !\n", __func__);
return -EINVAL;
}
// alloc 512 byte src memory and dst memory
src = dma_alloc_coherent(NULL, BUF_SIZE, &dma_src, GFP_KERNEL);
printk("src = 0x%x, dma_src = 0x%x\n", src, dma_src);
dst = dma_alloc_coherent(NULL, BUF_SIZE, &dma_dst, GFP_KERNEL);
printk("dst = 0x%x, dma_src = 0x%x\n", dst, dma_dst);
dma_cap_mask_t mask;
dma_cap_zero(mask);
//dma_cap_set(DMA_MEMCPY, mask); // direction: m2m
dma_cap_set(DMA_SLAVE, mask); // direction: m2m
chan = dma_request_channel(mask, pl330_filter, NULL); // request to dma channel
if(NULL == chan){
msleep(100);
chan = dma_request_channel(mask, NULL, NULL); // request to dma channel
}
if(NULL == chan)
printk("chan request failed !\n");
else
printk("chan OK!\n");
return 0;
}
module_init(exynos4_dma_init);
module_exit(exynos4_dma_exit);
MODULE_LICENSE("GPL");
然后是测试程序:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/ioctl.h>
#include <unistd.h>
#include <fcntl.h>
#define DEV_NAME "/dev/dma_m2m_test"
#define MEM_CPY_NO_DMA _IOW('L', 0x1210, int)
#define MEM_CPY_DMA _IOW('L', 0x1211, int)
int main(void)
{
int fd = -1,
ret = -1;
fd = open(DEV_NAME, O_RDWR);
if(fd < 0){
printf("open fail !\n");
exit(1);
}
ioctl(fd, MEM_CPY_NO_DMA, 0);
sleep(1);
ioctl(fd, MEM_CPY_DMA, 0);
sleep(1);
return 0;
}
之后是Makefile:
#指定内核源码路径
KERNEL_DIR = /home/george/1702/exynos/linux-3.5
#指定当前路径
CUR_DIR = $(shell pwd)
MYAPP = dma_test
#MODULE = spi_flash
MODULE = exynos4_dma
all:
make -C $(KERNEL_DIR) M=$(CUR_DIR) modules
arm-linux-gcc -o $(MYAPP) $(MYAPP).c
clean:
make -C $(KERNEL_DIR) M=$(CUR_DIR) clean
$(RM) $(MYAPP)
install:
cp -raf *.ko $(MYAPP) /home/george/1702/exynos/filesystem/1702
#指定当前项目编译的目标
obj-m = $(MODULE).o
下面是验证效果图:

另外,需要还有一点,上面我们用的是标准的DMA子系统API,有时候,我们用的是厂商封装后的接口,比如三星的,就把这些标准接口做了进一步封装,在三星的uart驱动中就是用的封装之后的API,这些封装的接口在dma-ops.c中,下面列出这些封装后的API:
static unsigned
samsung_dmadev_request(enum dma_ch dma_ch, struct samsung_dma_req *param);
static int
samsung_dmadev_release(unsigned ch, void *param);
static int
samsung_dmadev_config(unsigned ch, struct samsung_dma_config *param);
static int
samsung_dmadev_prepare(unsigned ch, struct samsung_dma_prep *param);
static inline int
samsung_dmadev_trigger(unsigned ch);
static inline int
samsung_dmadev_getposition(unsigned ch, dma_addr_t *src, dma_addr_t *dst);
static inline int
samsung_dmadev_flush(unsigned ch);
static struct samsung_dma_ops dmadev_ops = {
.request = samsung_dmadev_request,
.release = samsung_dmadev_release,
.config = samsung_dmadev_config,
.prepare = samsung_dmadev_prepare,
.trigger = samsung_dmadev_trigger,
.started = NULL,
.getposition = samsung_dmadev_getposition,
.flush = samsung_dmadev_flush,
.stop = samsung_dmadev_flush,
};
void *samsung_dmadev_get_ops(void)
{
return &dmadev_ops;
}
就说这么多吧. |