linux内核之dmaengine
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 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;
};
struct axidma_chns {
struct dma_chan *dma_chan;
unsigned char used;
#define DMA_STATUS_UNFINISHED 0
#define DMA_STATUS_FINISHED 1
unsigned 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;
}
更多推荐
所有评论(0)