特别注意: dma 搬运超时,和dma的配置时加上锁和关闭中断,否则会出现偶尔失败。
Zynq DMA Use demo
DMA App
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/ioctl.h>
#include <fcntl.h>
#define DRIVER_NAME "/dev/xxxxx"
#define AXIDMA_IOC_MAGIC 'A'
#define DMA_PS_TO_PL _IO(AXIDMA_IOC_MAGIC, 1)
#define DMA_PL_TO_PS _IO(AXIDMA_IOC_MAGIC, 2)
typedef struct
{
unsigned long long int src_addr; /* 8 Bytes*/
unsigned long long int dst_addr; /* 8 Bytes*/
size_t data_len;
}apg_cdma_config_t;
unsigned int read_dma(unsigned int high_addr, unsigned int base_addr, unsigned int ReadValue[], unsigned int num_bytes)
{
int fd = -1;
apg_cdma_config_t config = {0U};
int ret = 0;
/* open dev */
fd = open(DRIVER_NAME, O_RDWR);
if(fd < 0){
printf("open %s failed\n", DRIVER_NAME);
return -1;
}
config.data_len = num_bytes;
config.dst_addr = 0;
config.src_addr = (unsigned long long int)(((unsigned long long int)high_addr << 32) | ((unsigned long long int)base_addr << 0));
printf("%s: src %llx dst %llx len %d\r\n"
, __FUNCTION__
,config.src_addr
,config.dst_addr
,config.data_len);
/* get channel */
ret = ioctl(fd, DMA_PL_TO_PS, &config);
if(ret){
perror("ioctl");
printf("ioctl: get channel failed <%d>\n", ret);
goto error;
}
ret = lseek(fd, 0, SEEK_SET);
if (ret < 0){
printf("lseek: %d\n", ret);
return -1U;
}
ret = read(fd, ReadValue, num_bytes);
if (-1 == ret){
perror("write");
}
// printf("data: %x %x %x %x\r\n", ReadValue[0], ReadValue[1], ReadValue[2], ReadValue[3]);
close(fd);
/* for test */
return ReadValue[num_bytes/4];
error:
close(fd);
return -1;
}
unsigned int write_dma(unsigned int high_addr, unsigned int base_addr, unsigned int WriteValue[], unsigned int num_bytes) {
int fd = -1;
int ret;
apg_cdma_config_t config = {0U};
/* open dev */
fd = open(DRIVER_NAME, O_RDWR);
if(fd < 0){
printf("open %s failed\n", DRIVER_NAME);
return -1;
}
ret = write(fd, WriteValue, num_bytes);
if (-1 == ret){
perror("write");
}
config.data_len = num_bytes;
config.dst_addr = (unsigned long long int)(((unsigned long long int)high_addr << 32) | ((unsigned long long int)base_addr << 0));
config.src_addr = 0;
printf("%s: src %llx dst %llx len %d\r\n"
, __FUNCTION__
,config.src_addr
,config.dst_addr
,config.data_len);
/* get channel */
ret = ioctl(fd, DMA_PS_TO_PL, &config);
if(ret){
perror("ioctl");
printf("ioctl: get channel failed <%d>\n", ret);
goto error;
}
close(fd);
error:
close(fd);
return -1;
}
#define DMA_MEMCPY_LEN 16
unsigned int datadst[4] = {0};
/*
* input: HEX string, The HEX string need have 0x[] or 0X[] perfix.
* return: return error will value -eq 0xffffffffffffffff.
* */
unsigned long long int str2hex(char *hexstr)
{
unsigned long long int hexvalue = 0;
unsigned char value = 0;
if ( ((hexstr[0] == '0') && (hexstr[1] == 'x')) || \
((hexstr[0] == '0') && (hexstr[1] == 'X')) )
{
for(unsigned char i=2; (i<=17 && hexstr[i] != '\0'); i++){
if ((hexstr[i] >= '0') && (hexstr[i] <= '9')){
value = hexstr[i] - '0' + 0;
}else if ((hexstr[i] >= 'a') && (hexstr[i] <= 'f')){
value = hexstr[i] - 'a' + 10;
}else if ((hexstr[i] >= 'A') && (hexstr[i] <= 'F')) {
value = hexstr[i] - 'A' + 10;
}else{
printf("hex string error: %s\r\n", hexstr);
return -1;
}
hexvalue = hexvalue << 4;
hexvalue |= value;
}
}else{
printf("hex string error: %s\r\n", hexstr);
return -1;
}
return hexvalue;
}
int main(const int argc, char * argv[])
{
int fd = -1;
int ret;
int i=0, j=0;
apg_cdma_config_t config = {0U};
unsigned long long int pl_addr = 0u;
/* for test */
printf("RUN: %s %s\r\n",__FILE__ ,__TIME__);
switch(argc){
case 2:{
pl_addr = str2hex(argv[1]);
if (pl_addr == 0xffffffffffffffff){
goto error;
}
// printf("str: %s addr: 0x%llx\n\r", argv[1], pl_addr);
read_dma((0xffffffff & (pl_addr>>32))
,(0xffffffff & (pl_addr>>0))
,datadst
,DMA_MEMCPY_LEN);
}break;
case 3:
case 4:
case 5:
case 6:{
pl_addr = str2hex(argv[1]);
if (pl_addr == 0xffffffffffffffff){
goto error;
}
// printf("str: %s addr: 0x%x\n\r", argv[1], pl_addr);
for (i=2, j=0; i<argc; i++, j++){
datadst[j] = str2hex(argv[i]);
if (datadst[j] == 0xffffffffffffffff){
goto error;
}
}
write_dma((0xffffffff & (pl_addr>>32))
,(0xffffffff & (pl_addr>>0))
,datadst
,4*j);
read_dma((0xffffffff & (pl_addr>>32))
,(0xffffffff & (pl_addr>>0))
,datadst
,4*j);
}break;
default:
goto error;
break;
}
printf("end data: %08x %08x %08x %08x\r\n", datadst[0], datadst[1], datadst[2], datadst[3]);
return 0;
error:
printf("Usage: %s [0x0000_0010_0000_0000] <0x0000_0000> <0x0000_0000> <0x0000_0000> <0x0000_0000>\r\n", argv[0]);
return -1;
}
DMA Driver
申请dma 并配置
// An highlighted block
/*
* date: 2022-11-23
*/
#include <linux/dma-mapping.h>
#include <linux/dmaengine.h>
#include <linux/platform_device.h>
#include <linux/device.h>
#include <linux/of_irq.h>
#include <linux/module.h>
#include <linux/uaccess.h>
#include <linux/cdev.h>
typedef struct
{
dma_addr_t src_addr; /* 8 Bytes*/
dma_addr_t dst_addr; /* 8 Bytes*/
size_t data_len;
}apg_cdma_config_t;
typedef struct
{
unsigned int *src; /* dma_alloc_coherent src vir addr */
unsigned int *dst; /* dma_alloc_coherent dst vir addr */
dma_addr_t dma_src; /* dma_alloc_coherent src phy addr */
dma_addr_t dma_dst; /* dma_alloc_coherent dst phy addr */
enum dma_ctrl_flags flags;
struct dma_chan *dma_chan;
wait_queue_head_t wait;
unsigned int callbacked:1;
}apg_cdma_info_t;
typedef struct
{
dev_t devno;
struct cdev cdev;
struct class *class;
struct device *device;
struct platform_device* pdev;
struct fasync_struct *fasync;
apg_cdma_info_t info;
unsigned char dev_count;
}apg_cdma_dev_t;
apg_cdma_dev_t apg_cdma_dev = {0};
static DEFINE_MUTEX(apg_cdma_mutex);
spinlock_t apg_cdma_lock;
#define BUF_SIZE 4096
#define AXIDMA_IOC_MAGIC 'A'
#define DMA_PS_TO_PL _IO(AXIDMA_IOC_MAGIC, 1)
#define DMA_PL_TO_PS _IO(AXIDMA_IOC_MAGIC, 2)
#if 0
=>setenv bootargs "root=/dev/ram0 ramdisk_size=0x10000000 cma=64M@0x0-0xa0000000"
Device Drivers -> Generic Driver Options -> Default contiguous memory area size 的 Size in Mega Bytes修改为25
===========> system-user.dtsi
&amba {
apg: apg-dma@0 {
#address-cells = <2>;
#size-cells = <2>;
compatible = "xxxxxxxxxx";
reg = <0x00000000 0x00000000 0x00000000 0x00080000
0x00000000 0x00000000 0x00000000 0x00000000>;
num-channel=<1>;
};
};
#endif
static bool filter(struct dma_chan *chan, void *param)
{
if (!strcmp(dma_chan_name(chan), "dma1chan0")){
// printk("filter: %s\r\n", dma_chan_name(chan));
return true;
}
else{
return false;
}
}
static int axidma_open(struct inode *inode, struct file *file)
{
dma_cap_mask_t mask;
// memset(apg_cdma_dev.info.src, 0xAA, BUF_SIZE);
// memset(apg_cdma_dev.info.dst, 0xBB, BUF_SIZE);
// printk("open src: 0x%llx dst: 0x%llx\r\n", *((unsigned long long *)(apg_cdma_dev.info.src)), *((unsigned long long *)(apg_cdma_dev.info.dst)));
mutex_lock(&apg_cdma_mutex);
dma_cap_zero(mask);
dma_cap_set(DMA_MEMCPY, mask);
//dma_cap_set(DMA_SLAVE, mask);
mutex_unlock(&apg_cdma_mutex);
apg_cdma_dev.info.dma_chan = dma_request_channel(mask, filter, &apg_cdma_dev);
if(!apg_cdma_dev.info.dma_chan){
printk("cdma request channel failed\n");
return -1;
}
apg_cdma_dev.info.src = dma_alloc_coherent(NULL, BUF_SIZE, &(apg_cdma_dev.info.dma_src), GFP_KERNEL);
apg_cdma_dev.info.dst = dma_alloc_coherent(NULL, BUF_SIZE, &(apg_cdma_dev.info.dma_dst), GFP_KERNEL);
return 0;
}
static ssize_t axidma_read(struct file *filp, char __user *buf, size_t count,
loff_t *pos)
{
int ret = 0;
mutex_lock(&apg_cdma_mutex);
ret = simple_read_from_buffer(buf, count, pos, apg_cdma_dev.info.dst,
BUF_SIZE);
// printk("axidma_read ret: %d\r\n", ret);
mutex_unlock(&apg_cdma_mutex);
return ret;
}
static ssize_t axidma_write(struct file *filp, const char __user *buf,
size_t count, loff_t *pos)
{
int ret = 0;
mutex_lock(&apg_cdma_mutex);
ret = simple_write_to_buffer(apg_cdma_dev.info.src, BUF_SIZE, pos, buf, count);
// printk("axidma_write ret: %d\r\n", ret);
mutex_unlock(&apg_cdma_mutex);
return ret;
}
static loff_t axidma_llseek(struct file *filp, loff_t offset, int whence)
{
int ret = -EFAULT;
if ((offset%4) == 0){
ret = default_llseek(filp, offset, whence);
}
return ret;
}
static int axidma_release(struct inode *inode, struct file *file)
{
// printk("close src: 0x%llx dst: 0x%llx\r\n", *((unsigned long long *)(apg_cdma_dev.info.src)), *((unsigned long long *)(apg_cdma_dev.info.dst)));
mutex_unlock(&apg_cdma_mutex);
dma_release_channel(apg_cdma_dev.info.dma_chan);
dma_free_coherent(NULL, BUF_SIZE, apg_cdma_dev.info.src, apg_cdma_dev.info.dma_src);
dma_free_coherent(NULL, BUF_SIZE, apg_cdma_dev.info.dst, apg_cdma_dev.info.dma_dst);
mutex_unlock(&apg_cdma_mutex);
return 0;
}
static void dma_complete_func(void *info)
{
apg_cdma_info_t *cdma_info = info;
unsigned long flags;
// printk("complete: 0x%llx dst: 0x%llx\r\n", *((unsigned long long *)(apg_cdma_dev.info.src)), *((unsigned long long *)(apg_cdma_dev.info.dst)));
// printk("dma_complete_func cdma\r\n");
spin_lock_irqsave(&apg_cdma_lock, flags);
cdma_info->callbacked = 1;
wake_up_interruptible(&(cdma_info->wait));
spin_unlock_irqrestore(&apg_cdma_lock, flags);
}
static long axidma_unlocked_ioctl(struct file *file, unsigned int cmd, unsigned long arg)
{
int ret = 0U;
struct dma_device *dma_dev;
struct dma_async_tx_descriptor *tx = NULL;
dma_cookie_t cookie;
enum dma_ctrl_flags dma_flags;
static apg_cdma_config_t config = {0U};
unsigned long flags;
if (arg == 0){
goto error;
}
if (copy_from_user(&config, (void __user *)arg, sizeof (apg_cdma_config_t))) {
goto error;
}else{
if(config.data_len%4){ // 对齐
goto error;
}
}
// printk("cpy: dst: %llx, src: %llx, len: %d\r\n"
// , config.dst_addr
// , config.src_addr
// , config.data_len);
switch(cmd)
{
case DMA_PS_TO_PL:
{
spin_lock_irqsave(&apg_cdma_lock, flags);
init_waitqueue_head(&apg_cdma_dev.info.wait);
apg_cdma_dev.info.flags = DMA_CTRL_ACK | DMA_PREP_INTERRUPT;
dma_dev = apg_cdma_dev.info.dma_chan->device;
spin_unlock_irqrestore(&apg_cdma_lock, flags);
// phy addr
tx = dma_dev->device_prep_dma_memcpy(apg_cdma_dev.info.dma_chan, config.dst_addr, apg_cdma_dev.info.dma_src, config.data_len, dma_flags);
if(!tx){
printk("%s failed to prepare DMA memcpy\n", __func__);
goto error;
}
spin_lock_irqsave(&apg_cdma_lock, flags);
apg_cdma_dev.info.callbacked = 0; // init sleep value for wait_event_interruptible_timeout
tx->callback = dma_complete_func; // set call back function
tx->callback_param = &apg_cdma_dev.info;
cookie = tx->tx_submit(tx); // submit the desc
spin_unlock_irqrestore(&apg_cdma_lock, flags);
if(dma_submit_error(cookie)) {
printk("failed to do DMA tx_submit");
}
dma_async_issue_pending(apg_cdma_dev.info.dma_chan); // begin dma transfer
ret = wait_event_interruptible_timeout(apg_cdma_dev.info.wait,
apg_cdma_dev.info.callbacked, (HZ*10));
if (ret > 0 && apg_cdma_dev.info.callbacked) {
// printk("normal exit cdma %d %d\r\n", ret, apg_cdma_dev.info.callbacked);
ret = 0;
} else {
if (!ret) {
printk("cdma: write timeout exit, PS -> 0x%llx, %d\r\n", config.dst_addr, config.data_len);
ret = -ETIMEDOUT;
}
printk("terminate exit cdma\r\n");
dmaengine_terminate_all(apg_cdma_dev.info.dma_chan);
}
break;
}
break;
case DMA_PL_TO_PS:
{
spin_lock_irqsave(&apg_cdma_lock, flags);
init_waitqueue_head(&apg_cdma_dev.info.wait);
apg_cdma_dev.info.flags = DMA_CTRL_ACK | DMA_PREP_INTERRUPT;
dma_dev = apg_cdma_dev.info.dma_chan->device;
spin_unlock_irqrestore(&apg_cdma_lock, flags);
// phy addr
tx = dma_dev->device_prep_dma_memcpy(apg_cdma_dev.info.dma_chan, apg_cdma_dev.info.dma_dst, config.src_addr, config.data_len, dma_flags);
if(!tx){
printk("%s failed to prepare DMA memcpy\n", __func__);
goto error;
}
spin_lock_irqsave(&apg_cdma_lock, flags);
apg_cdma_dev.info.callbacked = 0; // init sleep value for wait_event_interruptible_timeout
tx->callback = dma_complete_func; // set call back function
tx->callback_param = &apg_cdma_dev.info;
spin_unlock_irqrestore(&apg_cdma_lock, flags);
cookie = tx->tx_submit(tx); // submit the desc
if(dma_submit_error(cookie)) {
printk("failed to do DMA tx_submit");
}
dma_async_issue_pending(apg_cdma_dev.info.dma_chan); // begin dma transfer
ret = wait_event_interruptible_timeout(apg_cdma_dev.info.wait,
apg_cdma_dev.info.callbacked, (HZ*10));
if (ret > 0 && apg_cdma_dev.info.callbacked) {
// printk("normal exit cdma %d %d\r\n", ret, apg_cdma_dev.info.callbacked);
ret = 0;
} else {
if (!ret) {
printk("cdma: read timeout exit, 0x%llx -> PS, %d\r\n", config.src_addr, config.data_len);
ret = -ETIMEDOUT;
}
printk("terminate exit cdma\r\n");
dmaengine_terminate_all(apg_cdma_dev.info.dma_chan);
}
break;
}
break;
default:
printk(KERN_ERR "Don't support cmd [%d]\n", cmd);
break;
}
return ret;
error:
return -EINVAL;
}
/*
* Kernel Interfaces
*/
static struct file_operations apg_cdma_fops = {
.owner = THIS_MODULE,
.llseek = no_llseek,
.write = axidma_write,
.read = axidma_read,
.llseek = axidma_llseek,
.unlocked_ioctl = axidma_unlocked_ioctl,
.open = axidma_open,
.release = axidma_release,
};
static int apg_cdma_probe(struct platform_device *pdev)
{
int result;
int major;
int i;
int dev_count = 1;
unsigned long long arrary[8] = {0U};
result = of_property_read_u64_array(pdev->dev.of_node, "reg", arrary, 4);
if(result) {
printk (KERN_ERR "cannot register miscdev (err=%d)\n", result);
return result;
}
apg_cdma_dev.dev_count = dev_count;
result = alloc_chrdev_region(&apg_cdma_dev.devno, 0, dev_count, "dma"); // 注册设备号
printk("MAJOR = %d MINOR = %d\r\n", MAJOR(apg_cdma_dev.devno), MINOR(apg_cdma_dev.devno));
if(result < 0){
printk("alloc_chrdev_region error\r\n");
result = -EBUSY;
goto fail;
}
major = MAJOR(apg_cdma_dev.devno);
cdev_init(&apg_cdma_dev.cdev, &apg_cdma_fops); // 绑定字符设备操作函数集
result = cdev_add(&apg_cdma_dev.cdev, apg_cdma_dev.devno, dev_count); // 添加字符设备
if(result < 0){
printk("cdev_add error\r\n");
result = -EBUSY;
goto unregister_chrdev_region;
}
apg_cdma_dev.class = class_create(THIS_MODULE, "dma");
if (IS_ERR(apg_cdma_dev.class)) {
result = PTR_ERR(apg_cdma_dev.class);
goto cdev_del;
}
for(i=0U; i<dev_count; i++){
apg_cdma_dev.device = device_create(apg_cdma_dev.class, NULL, MKDEV(major, i), NULL,"dma%d",i);
if(IS_ERR(apg_cdma_dev.device)){
result = PTR_ERR(apg_cdma_dev.device);
goto destroy_class;
}
}
dev_set_drvdata(&pdev->dev, &apg_cdma_dev);
spin_lock_init(&apg_cdma_lock);
return 0;
destroy_class:
class_destroy(apg_cdma_dev.class);
cdev_del:
cdev_del(&(apg_cdma_dev.cdev));
unregister_chrdev_region:
unregister_chrdev_region(apg_cdma_dev.devno, dev_count);
fail:
return result;
}
static int apg_cdma_remove(struct platform_device *pdev)
{
int i;
int dev_count;
printk("%s\r\n", __FUNCTION__);
dev_count = apg_cdma_dev.dev_count;
for (i=0U; i< dev_count; i++){
device_destroy(apg_cdma_dev.class, apg_cdma_dev.devno + i);
}
class_destroy(apg_cdma_dev.class);
cdev_del(&(apg_cdma_dev.cdev));
unregister_chrdev_region(apg_cdma_dev.devno, dev_count);
return 0;
}
static const struct of_device_id apg_cdma_of_match[] = {
{ .compatible = "XXXXXXX", },
{}
};
static struct platform_driver apg_cdma_driver = {
.probe = apg_cdma_probe,
.remove = apg_cdma_remove,
.driver = {
.owner = THIS_MODULE,
.name = "dma_driver",
.of_match_table = apg_cdma_of_match,
},
};
static int __init apg_cdma_init(void)
{
printk("%s\r\n", __FUNCTION__);
return platform_driver_register(&apg_cdma_driver);
}
static void __exit apg_cdma_exit(void)
{
printk("%s\r\n", __FUNCTION__);
return platform_driver_unregister(&apg_cdma_driver);
}
module_init(apg_cdma_init);
module_exit(apg_cdma_exit);
//module_platform_driver(apg_cdma_driver);
MODULE_ALIAS("platform: XXXXXXX");
MODULE_DESCRIPTION("CDMA driver");
MODULE_AUTHOR("XXXXXXXXXXXX");
MODULE_LICENSE("GPL");