⑱tiny4412 Linux驱动开发之DMA子系统驱动程序

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;
}

就说这么多吧.

猜你喜欢

转载自blog.csdn.net/qq_23922117/article/details/82288230