dmaengine framwork主要分为两部分:DMA controller 和DMA engine API。涉及内核相关文档:Documentation/damengine目录、Documentation/devicetree/bindings/dma/、Documentation/DAM-API-HOWTO.txt\DMA-API.txt\DMA-attributes.txt

1、dma controller(provider的角度)

基于DMA的硬件地址使用的是总线地址而不是物理地址,总线地址是从设备角度看到的内存地址,物理地址是从CPU mmu控制器外围角度上看到的内存地址。在pc上,对于ISA和PCI而言,总系地址即为物理地址,但并不是每个平台都是如此。接口总线通过桥接电路连接,桥接电路会将I/O地址映射为不同的物理地址,例如在PREP(PowerPC Reference Platform)系统中,物理地址0在设备端看起来是0x80000000,而0通常又被映射为虚拟地址0xC0000000,所以同一个地址就具备了三重身份:物理地址0,总线地址0x80000000及虚拟地址0xC0000000。

(1)重要数据结构之struct dma_device

该结构体抽象了dma controller,部分成员说明如下:

channels:一个链表头,保存该controller支持的所有dma channel

cap_mask:一个bitmap,只是controller所具备的能力

DMA_MEMCPY:内存到内存的拷贝

DMA_SG:设备支持内存到内存的分散/聚合传输

DMA_XOR:设备在内存区域执行XOR操作,如raid5等

DMA_PQ:内存到内存的P+Q计算

DMA_SLAVE:设备能处理设备到内存传输,包括分散/聚合传输

DMA_CYCLIC:设备能处理循环传输,如音频传输

src_addr_widths:一个bitmap表示该controller支持哪些宽度的src类型,包括1、2、3、4、8、16、32、64等

dst_addr_widths:一个bitmap表示该controller支持哪些宽的的dst类型,包括1、2、3、4、8、16、32、64等

directions:一个bitmap表示该controller支持哪些传输方向,包括DMA_MEM_TO_MEM、DMA_MEM_TO_DEV、DMA_DEV_TO_MEM、DMA_DEV_TO_DEV。

(2)重要数据结构之struct dma_chan

用于抽象dam channel,部分成员说明如下:

device:指向该channel所在的dma controller

(3)重要数据结构之struct virt_dma_chan

用于抽象一个虚拟的dma channel,多个虚拟channel可以共用一个物理channel,并由软件调度多个传输请求,将多个虚拟channel的传输串行的在物理channel完成,部分成员说明如下:

chan:一个struct dma_chan类型的变量

task:一个tasklet,用于等待该虚拟channel上传输的完成

(4)controller需要实现的操作函数集

device_alloc_chan_resources:当client驱动调用dma_request_channel时会调用该函数,负责分配通道需要的资源

device_free_chan_resources:当client驱动释放dam_release_channel时将会调用该函数,负责释放通道需要的资源

device_prep_dma_xxx:为DMA传输准备描述符,xxx:controller具有啥cap_mask,就要实现相应的接口

device_issue_pending:从pending queue中取走第一个传输描述符并启动传输,当传输完成后将会移到列表中下一个传输描述符,可以在中断上下文中使用。

device_tx_status:获取传输状态

device_config:使用给定参数重新配置通道

device_pause:暂停通道的传输

device_resume:恢复通道的传输

device_terminate_all:停止通道中所有的传输(包括pending和正在进行的传输)

dma_async_device_register:把填充好的dma_device结构实体注册到内核中。

2、dma engine api(consumer的角度)

DMA传输可以分为4类:mem2mem、mem2dev、dev2mem、dev2dev。mem2mem传输内核称之为Async TX,后三者统称为slave-DMA传输。

linux内核在dma engine之上专门提供了一层针对mem2mem的简洁API,称之为async tx api(例如:async_memcpy, async_memset, async_xor等)。

(1)重要数据结构之struct dma_slave_config

该结构体包含了完成一次DMA传输所需要的所有可能参数,部分成员如下:

direction:如同controller的directions

src_addr:传输方向dev2mem或dev2dev时,读取数据的位置(通常是固定的FIFO地址)

dst_addr:传输方向是mem2dev或dev2dev时,写入数据的位置(通常是固定的FIFO地址)

(2)重要数据结构之struct dam_async_tx_descriptor

传输描述符用于描述一次DMA传输(类似一个文件句柄),部分成员说明如下:

cookie:一个整型数,用于追踪本次传输

flags:DMA_CTRL_XXX,例如:DMA_CTRL_REUSE:表明这个描述符可以被重复使用;DMA_CTRL_ACK:表明暂时不能被重复使用

tx_submit:controller driver提供的回调函数,用于把该描述符提交到待传输列表

callback、callback_param:传输完成的回调函数(及参数)

(3)操作API接口

struct dma_chan *dma_request_channel(dma_cap_mask_t mask, dma_filter_fn filter_fn, void *filter_param)

申请一个DMA channel,当filter_fn为NULL时函数只是简单的返回第一个满足mask参数的通道,对于slave和cyclic通道强烈推荐使用以获取一个特定的DMA通道

void dma_release_channel(struct dma_chan *chan)

释放通道

int dmaengine_slave_config(struct dma_chan *chan, struct dam_slave_config *config)

申请到一个dma channel之后,根据实际情况,对该channel进行参数配置

struct dma_async_tx_descriptor *dmaengine_prep_slave_sg( 
        struct dma_chan *chan, struct scatterlist *sgl, 
        unsigned int sg_len, enum dma_data_direction direction, 
        unsigned long flags);

获取传输描述符,用于在“scatter gather buffers”列表和总线设备之间进行DMA传输

struct dma_async_tx_descriptor *dmaengine_prep_dma_cyclic( 
        struct dma_chan *chan, dma_addr_t buf_addr, size_t buf_len, 
        size_t period_len, enum dma_data_direction direction);

获取传输描述符,常用于音频等场景中,在进行一定长度的dma传输(buf_addr&buf_len)的过程中,每传输一定的byte(period_len),就会调用一次传输完成的回调函数

struct dma_async_tx_descriptor *dmaengine_prep_interleaved_dma( 
        struct dma_chan *chan, struct dma_interleaved_template *xt, 
        unsigned long flags);

获取传输描述符,可进行不连续的、交叉的DMA传输,通常用在图像处理、显示等场景中。

dma_cookie_t dmaengine_submit(struct dma_async_tx_descriptor *desc)

一旦传输描述符准备好并且回调函数也加入后,该函数把传输描述符加入到DMA engine驱动的等待队列,但不会启动DMA操作

void dma_async_issue_pending(struct dma_chan *chan)

该函数启动DMA传输,此时如果通道是空闲的,等待队列中的第一个传输描述符将会启动DMA操作。

static inline enum dma_status dma_async_is_tx_complete(struct dma_chan *chan,                                                                     
    dma_cookie_t cookie, dma_cookie_t *last, dma_cookie_t *used)

通过该接口测试传输是否完成,也可以通过回调函数获取传输完成的消息

3、测试之驱动

/*
* dw axi dmac
* author: helb
* date: 2018-08-08
*/#include <linux/module.h>
#include <linux/moduleparam.h>
#include <linux/types.h>
#include <linux/string.h>
#include <linux/miscdevice.h>
#include <linux/fs.h>
#include <linux/init.h>
#include <asm/uaccess.h>
#include <linux/dmaengine.h>#define DRIVER_NAME       "axidma"
#define AXIDMA_IOC_MAGIC            'A'
#define AXIDMA_IOCGETCHN            _IO(AXIDMA_IOC_MAGIC, 0)
#define AXIDMA_IOCCFGANDSTART       _IO(AXIDMA_IOC_MAGIC, 1)
#define AXIDMA_IOCGETSTATUS         _IO(AXIDMA_IOC_MAGIC, 2)
#define AXIDMA_IOCRELEASECHN        _IO(AXIDMA_IOC_MAGIC, 3)#define AXI_DMA_MAX_CHANS           8#define DMA_CHN_UNUSED         0
#define DMA_CHN_USED        1struct axidma_chncfg {unsigned int src_addr;unsigned int dst_addr;unsigned int len;unsigned char chn_num;unsigned char status;unsigned char reserve[2];unsigned int reserve2;
};struct axidma_chns {struct dma_chan *dma_chan;unsigned char used;
#define DMA_STATUS_UNFINISHED   0
#define DMA_STATUS_FINISHED     1unsigned char status;unsigned char reserve[2];
};struct axidma_chns channels[AXI_DMA_MAX_CHANS];static int axidma_open(struct inode *inode, struct file *file)
{printk("Open: do nothing\n");return 0;
}static int axidma_release(struct inode *inode, struct file *file)
{printk("Release: do nothing\n");return 0;
}static ssize_t axidma_write(struct file *file, const char __user *data, size_t len, loff_t *ppos)
{printk("Write: do nothing\n");return 0;
}static void dma_complete_func(void *status)
{*(char *)status = DMA_STATUS_FINISHED;printk("dma_complete!\n");
}static long axidma_unlocked_ioctl(struct file *file, unsigned int cmd, unsigned long arg)
{struct dma_device *dma_dev;struct dma_async_tx_descriptor *tx = NULL;dma_cap_mask_t mask;dma_cookie_t cookie;enum dma_ctrl_flags flags;struct axidma_chncfg chncfg;int ret = -1;int i;memset(&chncfg, 0, sizeof(struct axidma_chncfg));switch(cmd){case AXIDMA_IOCGETCHN:{for(i=0; i<AXI_DMA_MAX_CHANS; i++) {if(DMA_CHN_UNUSED == channels[i].used)break;               }if(AXI_DMA_MAX_CHANS == i){printk("Get dma chn failed, because no idle channel\n");goto error;}else{channels[i].used = DMA_CHN_USED;channels[i].status = DMA_STATUS_UNFINISHED;chncfg.chn_num = i;chncfg.status = DMA_STATUS_UNFINISHED;}dma_cap_zero(mask);dma_cap_set(DMA_MEMCPY, mask); channels[i].dma_chan = dma_request_channel(mask, NULL, NULL);if(!channels[i].dma_chan){printk("dma request channel failed\n");channels[i].used = DMA_CHN_UNUSED;goto error;}ret = copy_to_user((void __user *)arg, &chncfg, sizeof(struct axidma_chncfg));if(ret){printk("Copy to user failed\n");goto error;}}break;case AXIDMA_IOCCFGANDSTART:{ret = copy_from_user(&chncfg, (void __user *)arg, sizeof(struct axidma_chncfg));if(ret){printk("Copy from user failed\n");goto error;}if((chncfg.chn_num >= AXI_DMA_MAX_CHANS) || (!channels[chncfg.chn_num].dma_chan)){printk("chn_num[%d] is invalid\n", chncfg.chn_num);goto error;}dma_dev = channels[chncfg.chn_num].dma_chan->device;flags = DMA_CTRL_ACK | DMA_PREP_INTERRUPT;tx = dma_dev->device_prep_dma_memcpy(channels[chncfg.chn_num].dma_chan, chncfg.dst_addr, chncfg.src_addr, chncfg.len, flags);if(!tx){printk("Failed to prepare DMA memcpy\n");goto error;}tx->callback = dma_complete_func;channels[chncfg.chn_num].status = DMA_STATUS_UNFINISHED;tx->callback_param = &channels[chncfg.chn_num].status;cookie =  tx->tx_submit(tx);if(dma_submit_error(cookie)){printk("Failed to dma tx_submit\n");goto error;}dma_async_issue_pending(channels[chncfg.chn_num].dma_chan);}break;case AXIDMA_IOCGETSTATUS:{ret = copy_from_user(&chncfg, (void __user *)arg, sizeof(struct axidma_chncfg));if(ret){printk("Copy from user failed\n");goto error;}if(chncfg.chn_num >= AXI_DMA_MAX_CHANS){printk("chn_num[%d] is invalid\n", chncfg.chn_num);goto error;}chncfg.status = channels[chncfg.chn_num].status;ret = copy_to_user((void __user *)arg, &chncfg, sizeof(struct axidma_chncfg));if(ret){printk("Copy to user failed\n");goto error;}}break;case AXIDMA_IOCRELEASECHN:{ret = copy_from_user(&chncfg, (void __user *)arg, sizeof(struct axidma_chncfg));if(ret){printk("Copy from user failed\n");goto error;}if((chncfg.chn_num >= AXI_DMA_MAX_CHANS) || (!channels[chncfg.chn_num].dma_chan)){printk("chn_num[%d] is invalid\n", chncfg.chn_num);goto error;}dma_release_channel(channels[chncfg.chn_num].dma_chan);channels[chncfg.chn_num].used = DMA_CHN_UNUSED;channels[chncfg.chn_num].status = DMA_STATUS_UNFINISHED;}break;default:printk("Don't support cmd [%d]\n", cmd);break;}return 0;
error:return -EFAULT;
}/**    Kernel Interfaces*/static struct file_operations axidma_fops = {.owner        = THIS_MODULE,.llseek        = no_llseek,.write        = axidma_write,.unlocked_ioctl = axidma_unlocked_ioctl,.open        = axidma_open,.release    = axidma_release,
};static struct miscdevice axidma_miscdev = {.minor        = MISC_DYNAMIC_MINOR,.name        = DRIVER_NAME,.fops        = &axidma_fops,
};static int __init axidma_init(void)
{int ret = 0;ret = misc_register(&axidma_miscdev);if(ret) {printk (KERN_ERR "cannot register miscdev (err=%d)\n", ret);return ret;}memset(&channels, 0, sizeof(channels));return 0;
}static void __exit axidma_exit(void)
{    misc_deregister(&axidma_miscdev);
}module_init(axidma_init);
module_exit(axidma_exit);MODULE_AUTHOR("hlb");
MODULE_DESCRIPTION("Axi Dmac Driver");
MODULE_LICENSE("GPL");

4、测试之用户程序

#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>
#include <sys/mman.h>#define DRIVER_NAME      "/dev/axidma"#define AXIDMA_IOC_MAGIC             'A'
#define AXIDMA_IOCGETCHN            _IO(AXIDMA_IOC_MAGIC, 0)
#define AXIDMA_IOCCFGANDSTART       _IO(AXIDMA_IOC_MAGIC, 1)
#define AXIDMA_IOCGETSTATUS         _IO(AXIDMA_IOC_MAGIC, 2)
#define AXIDMA_IOCRELEASECHN        _IO(AXIDMA_IOC_MAGIC, 3)#define DMA_STATUS_UNFINISHED   0
#define DMA_STATUS_FINISHED     1
struct axidma_chncfg {unsigned int src_addr;unsigned int dst_addr;unsigned int len;unsigned char chn_num;unsigned char status;unsigned char reserve[2];unsigned int reserve2;
};#define SRC_ADDR      0x60000000
#define DST_ADDR        0x70000000
#define DMA_MEMCPY_LEN  0x300000
int main(void)
{struct axidma_chncfg chncfg;int fd = -1;int ret;printf("AXI dma test, only support mem to mem: copy 0x60000000 to 0x70000000, size:3M\n");/* for test */read_org_data();/* open dev */fd = open(DRIVER_NAME, O_RDWR);if(fd < 0){printf("open %s failed\n", DRIVER_NAME);return -1;}/* get channel */ret = ioctl(fd, AXIDMA_IOCGETCHN, &chncfg);if(ret){printf("ioctl: get channel failed\n");goto error;}printf("channel: %d\n", chncfg.chn_num);/* config addr */chncfg.src_addr = SRC_ADDR;chncfg.dst_addr = DST_ADDR;chncfg.len = DMA_MEMCPY_LEN;ret = ioctl(fd, AXIDMA_IOCCFGANDSTART, &chncfg);if(ret){printf("ioctl: config and start dma failed\n");goto error;}/* wait finish */while(1){ret = ioctl(fd, AXIDMA_IOCGETSTATUS, &chncfg);if(ret){printf("ioctl: get status failed\n");goto error;}if (DMA_STATUS_FINISHED == chncfg.status){break;}printf("status:%d\n", chncfg.status);sleep(1);}/* release channel */ret = ioctl(fd, AXIDMA_IOCRELEASECHN, &chncfg);if(ret){printf("ioctl: release channel failed\n");goto error;}close(fd);/* for test */read_new_data();return 0;
error:close(fd);return -1;
}

linux内核之dmaengine相关推荐

  1. Linux内核源码组织结构

    本文主要参考韦东山老师的<嵌入式Linux应用开发完全手册>,基于Linux-2.6.32.2源码. 概要:本文内容包含Linux源码树结构分析.Linux Makefile分析.Kcon ...

  2. Linux 内核,30 年C 语言将升级至 C11

    Linux 内核,30 年C 语言将升级至 C11 还在使用 89 年版 C 语言的 Linux 内核,现在终于要做出改变了.今天,Linux 开源社区宣布,未来会把内核 C 语言版本升级到 C11, ...

  3. linux内核开机显示企鹅logo,批改linux内核kernel开机logo(小企鹅)

    修改linux内核kernel开机logo(小企鹅) 修改linux内核kernel的开机图片(原为小企鹅图片). 转载请注明出处:http://blog.csdn.net/wang_zheng_ka ...

  4. linux内核内存管理(zone_dma zone_normal zone_highmem)

    Linux 操作系统和驱动程序运行在内核空间,应用程序运行在用户空间,两者不能简单地使用指针传递数据,因为Linux使用的虚拟内存机制,用户空间的数据可能被换出,当内核空间使用用户空间指针时,对应的数 ...

  5. Linux内核分析——可执行程序的装载

    链接的过程 首先运行C预处理器cpp,将C的源程序(a.c)翻译成ASCII码的中间文件(a.i) 接着C编译器ccl,将a.i翻译成ASCII汇编语言文件a.s 接着运行汇编器as,将a.s翻译成可 ...

  6. 【内核】嵌入式linux内核的五个子系统

    Perface Linux内核主要由进程调度(SCHED).内存管理(MM).虚拟文件系统(VFS).网络接口(NET)和进程间通信(IPC)5个子系统组成,如图1所示. 图1 Linux内核的组成部 ...

  7. 如何安装新linux内核,详解Debian系统中安装Linux新内核的流程

    一直对Linux内核很有兴趣,但苦于入门不易,认真看了ldd前5章突然就来感觉了,光看不练不顶用,首先就需要环境搭建. 使用的是Debian 5.0,内核2.6.26,欲安装的新内核为2.6.28,这 ...

  8. linux内核 机器码,u-boot与Linux内核机器码问题

    在<>一文中,执行完第6步的操作后,启动u-boot后,用bootm 命令来引导内核(执行bootm 0x30008000),但是执行后,卡住了,无法启动内核,现象如下: Starting ...

  9. linux内核远程漏洞,CVE-2019-11815:Linux内核竞争条件漏洞导致远程代码执行

    *本文中涉及到的相关漏洞已报送厂商并得到修复,本文仅限技术研究与讨论,严禁用于非法用途,否则产生的一切后果自行承担. 运行了Linux发行版的计算机设备,如果内核版本小于5.0.8的话,将有可能受到一 ...

  10. linux内核添加c代码,如何从C代码加载Linux内核模块?

    Ciro Santill.. 19 最小的可运行示例 使用这个简单的参数打印机模块在QEMU + Buildroot VM和Ubuntu 16.04主机上进行了测试. 我们使用init_module/ ...

最新文章

  1. python顺序表数组_数据结构 | 顺序表
  2. linux 下运行libnids,libnids出错
  3. 跨屏html ui,Amaze UI(HTML5 跨屏前端框架) v2.7.2
  4. java 防御编程_用Java编程。实现两个人对决。有血量有防御。有攻击力
  5. 微软将所有的Windows代码库迁移到Git
  6. 《信息安全系统设计基础》第六周学习总结
  7. VirtualBox 搭建android-x86
  8. Unreal Engine 4:虚幻4 文档
  9. 导出 excel表格(数据、echarts图片)
  10. 什么牌子真无线蓝牙耳机适合运动,高续航舒适小巧这五款蓝牙耳机不要错过
  11. openairinterface5g基站老版eNB部署
  12. 南非认证_南非2008-避免或尽量减少时差
  13. Git error: cannot spawn ssh: No such file or directory的一个解决办法
  14. 第一届华数杯A题思路分析
  15. linux系统读取plc状态,Linux系统下上位机通讯协议及PLC冗余系统组态-工业支持中心-西门子中国...
  16. 移动硬盘部分分区不能识别解决方法
  17. 关于以太网光纤收发器,逻辑隔离与物理隔离的理解与区别
  18. Informatica Big Data Management 运维命令
  19. 八一钢铁:宝钢入主,中报业绩大幅增长
  20. KV260 AI入门开发套件简介

热门文章

  1. bzoj2539: [Ctsc2000]丘比特的烦恼
  2. CSS3知识点总结---transitionend
  3. putty+Xming使用方法
  4. vs2012中EF6的BUG
  5. .net中对时间的操作
  6. ASP.NET数据分页技术(4)
  7. 建议能在园子里面发布Silverlight2.0应用
  8. WaitForMultipleObjects、WaitForSingleObject、GetExitCodeThread
  9. 怎么自动删除以前数据脚本_移动硬盘数据删除了怎么恢复?硬盘恢复软件分享!...
  10. anaconda 卸载_Windows安装Anaconda使用教程