DMA(Direct Memory Access,直接内存访问)是一种不经过CPU而直接从内存存取数据的数据交换模式。在需要进行大量数据交换的场合,用好DMA,可以大大提高系统的性能,因为DMA操作几乎不占用CPU资源。s3c2440提供了4个通道的DMA
每个DMA通道能处理下面四种情况的数据传输:
(1)源器件和目的器件都在系统总线 APB
(2)源器件在系统总线,目的器件在外设总线
(3)源器件在外设总线,目的器件在系统总线
(4)源器件和目的器件都在外设总线 AHB
下面的DMA驱动程序使用的是第四种,内存属于AHB总线上的,我们打算在内存中开辟两个连续空间,分别作为源和目的。我们用两个方法将源中的数据写到目的中,一种方法是让cpu去做,另外一种方法是让DMA去做:
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#define MEM_CPY_NO_DMA 0
#define MEM_CPY_DMA 1
#define BUF_SIZE (512*1024)
#define DMA0_BASE_ADDR 0x4B000000
#define DMA1_BASE_ADDR 0x4B000040
#define DMA2_BASE_ADDR 0x4B000080
#define DMA3_BASE_ADDR 0x4B0000C0
struct s3c_dma_regs {
unsigned long disrc;
unsigned long disrcc;
unsigned long didst;
unsigned long didstc;
unsigned long dcon;
unsigned long dstat;
unsigned long dcsrc;
unsigned long dcdst;
unsigned long dmasktrig;
};
static int major = 0;
static char *src;
static u32 src_phys;
static char *dst;
static u32 dst_phys;
static struct class *cls;
static volatile struct s3c_dma_regs *dma_regs;
static DECLARE_WAIT_QUEUE_HEAD(dma_waitq);
/* 中断事件标志, 中断服务程序将它置1,ioctl将它清0 */
static volatile int ev_dma = 0;
static int s3c_dma_ioctl(struct inode *inode, struct file *file, unsigned int cmd, unsigned long arg)
{
int i;
memset(src, 0xAA, BUF_SIZE);
memset(dst, 0x55, BUF_SIZE);
switch (cmd)
{
//这是非DMA模式
case MEM_CPY_NO_DMA :
{
for (i = 0; i < BUF_SIZE; i++)
dst[i] = src[i]; //CPU直接将源拷贝到目的
if (memcmp(src, dst, BUF_SIZE) == 0)
{
printk("MEM_CPY_NO_DMA OKn");
}
else
{
printk("MEM_CPY_DMA ERRORn");
}
break;
}
//这是DMA模式
case MEM_CPY_DMA :
{
ev_dma = 0;
/* 把源,目的,长度告诉DMA */
/* 关于下面寄存器的具体情况,我们在注释3里面来详细讲一下 */
dma_regs->disrc = src_phys; /* 源的物理地址 */
dma_regs->disrcc = (0<<1) | (0<<0); /* 源位于AHB总线, 源地址递增 */
dma_regs->didst = dst_phys; /* 目的的物理地址 */
dma_regs->didstc = (0<<2) | (0<<1) | (0<<0); /* 目的位于AHB总线, 目的地址递增 */
dma_regs->dcon = (1<<30)|(1<<29)|(0<<28)|(1<<27)|(0<<23)|(0<<20)|(BUF_SIZE<<0); /* AHP总线同步,使能中断,全速传输(BIT27),软件触发(BIT23), */
/* 启动DMA */
dma_regs->dmasktrig = (1<<1) | (1<<0); //打开通道,软件启动
/* 如何知道DMA什么时候完成? */
/* 休眠 */
wait_event_interruptible(dma_waitq, ev_dma);
if (memcmp(src, dst, BUF_SIZE) == 0)
{
printk("MEM_CPY_DMA OKn");
}
else
{
printk("MEM_CPY_DMA ERRORn");
}
break;
}
}
return 0;
}
static struct file_operations dma_fops = {
.owner = THIS_MODULE,
.ioctl = s3c_dma_ioctl,
};
static irqreturn_t s3c_dma_irq(int irq, void *devid)
{
/* 唤醒 */
ev_dma = 1;
wake_up_interruptible(&dma_waitq); /* 唤醒休眠的进程 */
return IRQ_HANDLED;
}
static int s3c_dma_init(void)
{
/* 这里注册一个中断,当DMA数据传输完毕之后会发生此中断 */
if (request_irq(IRQ_DMA3, s3c_dma_irq, 0, "s3c_dma", 1))
{
printk("can't request_irq for DMAn");
return -EBUSY;
}
/* 分配SRC, DST对应的缓冲区:之前我们知道在内核中开辟空间可以用kmalloc函数,这里却用了dma_alloc_writecombine,这是为什么呢?这是因为kmalloc开辟的空间其逻辑地址虽然是连续的,但是其实际的物理地址可能不是连续的。而DMA传输数据时,要求物理地址是连续的,dma_alloc_writecombine就满足这一点,这个函数的原型是:dma_alloc_writecombine(struct device *dev, size_t size, dma_addr_t *handle, gfp_t gfp)其中size代表开辟的空间的大小,handle代表开辟的空间的物理地址,返回值是开辟的空间的逻辑地址*/
src = dma_alloc_writecombine(NULL, BUF_SIZE, &src_phys, GFP_KERNEL);//源
if (NULL == src)
{
printk("can't alloc buffer for srcn");
free_irq(IRQ_DMA3, 1);
return -ENOMEM;
}
dst = dma_alloc_writecombine(NULL, BUF_SIZE, &dst_phys, GFP_KERNEL);//目的
if (NULL == dst)
{
free_irq(IRQ_DMA3, 1);
dma_free_writecombine(NULL, BUF_SIZE, src, src_phys);
printk("can't alloc buffer for dstn");
return -ENOMEM;
}
major = register_chrdev(0, "s3c_dma", &dma_fops);//注册字符设备
/* 为了自动创建设备节点 */
cls = class_create(THIS_MODULE, "s3c_dma");
class_device_create(cls, NULL, MKDEV(major, 0), NULL, "dma"); /* /dev/dma */
dma_regs = ioremap(DMA3_BASE_ADDR, sizeof(struct s3c_dma_regs));//这边是将DMA控制寄存器映射到内核空间
return 0;
}
static void s3c_dma_exit(void)
{
iounmap(dma_regs);
class_device_destroy(cls, MKDEV(major, 0));
class_destroy(cls);
unregister_chrdev(major, "s3c_dma");
dma_free_writecombine(NULL, BUF_SIZE, src, src_phys);
dma_free_writecombine(NULL, BUF_SIZE, dst, dst_phys);
free_irq(IRQ_DMA3, 1);
}
module_init(s3c_dma_init);
module_exit(s3c_dma_exit);
MODULE_LICENSE("GPL");
测试程序:
#include
#include
#include
#include
#include
#include
/* ./dma_test nodma
* ./dma_test dma
*/
#define MEM_CPY_NO_DMA 0
#define MEM_CPY_DMA 1
void print_usage(char *name)
{
printf("Usage:n");
printf("%s
}
int main(int argc, char **argv)
{
int fd;
if (argc != 2)
{
print_usage(argv[0]);
return -1;
}
fd = open("/dev/dma", O_RDWR);
if (fd < 0)
{
printf("can't open /dev/dman");
return -1;
}
if (strcmp(argv[1], "nodma") == 0)
{
while (1)
{
ioctl(fd, MEM_CPY_NO_DMA);
}
}
else if (strcmp(argv[1], "dma") == 0)
{
while (1)
{
ioctl(fd, MEM_CPY_DMA);
}
}
else
{
print_usage(argv[0]);
return -1;
}
return 0;
}
测试方法:
# insmod dma.ko //加载驱动
# cat /proc/interrupts //查看中断
CPU0
30: 52318 s3c S3C2410 Timer Tick
33: 0 s3c s3c-mci
34: 0 s3c I2SSDI
35: 0 s3c I2SSDO
36: 0 s3c s3c_dma
37: 12 s3c s3c-mci
42: 0 s3c ohci_hcd:usb1
43: 0 s3c s3c2440-i2c
51: 2725 s3c-ext eth0
60: 0 s3c-ext s3c-mci
70: 97 s3c-uart0 s3c2440-uart
71: 100 s3c-uart0 s3c2440-uart
83: 0 - s3c2410-wdt
Err: 0
# ls /dev/dma //查看设备
/dev/dma
# ./dmatest //如此就会打印用法
Usage:
./dmatest
# ./dmatest dma //以DMA方式拷贝,CPU可以做其他事情
ME
『本文转载自网络,版权归原作者所有,如有侵权请联系删除』