驱动程序:
#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/pci.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)
#define USE_SINGLE 0 // 0 use dma_alloc_coherent ,1 use dma_map_single
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* param)
{
if(0 == memcmp(src, dst, BUF_SIZE))
printk("MEM_CPY_DMA OK\n");
else
printk("MEM_CPY_DMA ERROR\n");
}
int my_dma_open(struct inode *inode, struct file *filp)
{
return 0;
}
long my_dma_ioctl(struct file *filp, unsigned int cmd, unsigned long arg)
{
int i = 0;
if !USE_SINGLE
memset(src, 0xAA, BUF_SIZE);
memset(dst, 0xBB, BUF_SIZE);
#endif
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 my_dma_release(struct inode *inode, struct file *filp)
{
return 0;
}
const struct file_operations my_dma_fops = {
.open = my_dma_open,
.unlocked_ioctl = my_dma_ioctl,
.release = my_dma_release,
};
struct miscdevice dma_misc = {
.minor = MISC_DYNAMIC_MINOR,
.name = "dma_m2m_test",
.fops = &my_dma_fops,
};
static void __exit my_dma_exit(void)
{
if(chan)
{
dma_release_channel(chan);
dma_free_coherent(chan->device->dev, BUF_SIZE, src, dma_src);
dma_free_coherent(chan->device->dev, BUF_SIZE, dst, dma_dst);
}
misc_deregister(&dma_misc);
}
static int __init my_dma_init(void)
{
int ret = misc_register(&dma_misc);
dma_cap_mask_t mask;
if(ret < 0)
{
printk("%s misc register failed !\n", __func__);
return -EINVAL;
}
printk("%s misc register ok !\n", __func__);
//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, NULL, NULL);
if(NULL == chan)
{
msleep(100);
chan = dma_request_channel(mask, NULL, NULL); // request to dma channel
}
if(NULL == chan)
{
printk("chan request failed !\n");
return -EINVAL;
}
else
{
printk("chan OK!\n");
if(chan->device && chan->device->dev)
printk("chan name %s \n",dev_name(chan->device->dev) );
if(chan->dev)
printk("sys name %s \n",dev_name(&(chan->dev->device)));
if(chan->slave)
printk("slave name %s \n",dev_name(chan->slave));
#if !USE_SINGLE
// alloc 512 byte src memory and dst memory
src = dma_alloc_coherent(chan->device->dev, BUF_SIZE, &dma_src, GFP_KERNEL);
printk("src = %p, dma_src = %llu\n", src, dma_src);
dst = dma_alloc_coherent(chan->device->dev, BUF_SIZE, &dma_dst, GFP_KERNEL);
printk("dst = %p, dma_src = %llu\n", dst, dma_dst);
#else
src = devm_kzalloc(chan->device->dev, BUF_SIZE, GFP_KERNEL);
if(NULL == src)
{
printk(KERN_INFO"alloc src buffer error\n");
return -EINVAL;
}
dst = devm_kzalloc(chan->device->dev, BUF_SIZE, GFP_KERNEL);
if(NULL == dst )
{
printk(KERN_INFO"alloc src buffer error\n");
return -EINVAL;
}
memset(src, 0xAA, BUF_SIZE); //right, should before dma_map_single
memset(dst, 0xBB, BUF_SIZE); //right, should before dma_map_single
dma_src = dma_map_single(chan->device->dev, src, BUF_SIZE, DMA_BIDIRECTIONAL);
dma_dst = dma_map_single(chan->device->dev, dst, BUF_SIZE, DMA_BIDIRECTIONAL);
//memset(src, 0xAA, BUF_SIZE); //wrong, after dma_map_single, data will be incorrect
//memset(dst, 0xBB, BUF_SIZE); //wrong, after dma_map_single, data will be incorrect
printk("single src = %p, dma_src = %llu\n", src, dma_src);
printk("single dst = %p, dma_src = %llu\n", dst, dma_dst);
#endif
}
return 0;
}
module_init(my_dma_init);
module_exit(my_dma_exit);
MODULE_LICENSE("GPL");
app测试程序:
#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:
obj-m = dmatest.o
kernel_dir = /home/XX/kernel/
all:
make -C $(kernel_dir) ARCH=arm64 M=${PWD} modules
$(CROSS_COMPILE)gcc -o myapp myapp.c
clean:
make -C $(kernel_dir) ARCH=arm64 M=${PWD} clean
执行情况:
root@rk3588-buildroot:/data/my-dmatest# insmod dmatest.ko
[ 74.346086] my_dma_init misc register ok !
root@rk3588-buildroot:/data/my-dmatest# [ 74.346144] chan OK!
[ 74.346151] chan name fea10000.dma-controller
[ 74.346157] sys name dma0chan2
[ 74.346206] src = 00000000e159c068, dma_src = 2351104
[ 74.346222] dst = 0000000066a0dfd3, dma_src = 2363392
root@rk3588-buildroot:/data/my-dmatest# ./myapp
[ 82.427124] MEM_CPY_NO_DMA OK
[ 83.427635] MEM_CPY_DMA OK
参考文档:
解读Linux内核DMA子系统驱动开发程序 - 知乎 (zhihu.com)
linux驱动之DMA - 简书 (jianshu.com)
ps:参考文档在我的rk3588上编译不过,运行的时候也会出问题,已经修改了,
执行正常,验证通过