全志H3平台DMA框架

概要

Dmaengine是linux内核dma驱动框架,针对DMA驱动的混乱局面内核社区提出了一个全新的框架驱动,目标在统一dma
API让各个模块使用DMA时不用关心硬件细节,同时代码复用提高,并且实现异步的数据传输,降低机器负载。

1.1 基本结构

dmaengine向其他模块提供接口;virt-dma,Virtual DMA向dmaengine提供初始化函数,传输各阶段状态登记链表,desc_free函数等;dma
drivers为具体DMA控制器的驱动代码,其通过dma_async_device_register()注册到dmaengine。

2 代码架构

lli: linked list ltem, the DMA block descriptor

2.1 源码分析

linux-3.4/drivers/dma

dmaengine.c

注册dma类,在调用dma_async_device_register注册具体平台的DMA设备时会用到。

sunxi-dma.c

sunxi_probe解析:

1.1  ret = request_irq(irq, sunxi_dma_interrupt, IRQF_SHARED,dev_name(&pdev->dev), sunxi_dev);//注册中断

1.2 clk_get(&pdev->dev, "dma"); //获取时钟

drivers/clk/sunxi/ clk-sun8iw7.c

SUNXI_CLK_PERIPH (dma,0,0,0,0,0,0,0,0,0,0,BUS_RST0,BUS_GATE0,0,0,6, 6,0,&clk_lock,NULL, 0)

struct periph_init_data sunxi_periphs_init[] = {

{"dma",0,ahb1mod_parents,ARRAY_SIZE(ahb1mod_parents),&sunxi_clk_periph_dma}

}

drivers/clk/sunxi/clk-periph.h 定义SUNXI_CLK_PERIPH宏

sunxi_init_clocks -> sunxi_clk_register_periph -> clk_register(NULL, &periph->hw);

1.3 dma_pool_create, 创建一个一致内存块池,其参数name是DMA池的名字,用于诊断用,参数dev是将做DMA的设备,参数size是DMA池里的块的大小,参数align是块的对齐要求,是2的幂,参数allocation返回没有跨越边界的块数(或0)。

1.4 vchan_init初始化virt_dma_chan

1.5初始化sunxi_dev->dma_dev结构成员后,调用dma_async_device_register(&sunxi_dev->dma_dev)注册到dmaengine

1.5.1 get_dma_id(device) //建立idr机制,分配一个新idr entry,将返回值存储在&sunxi_dev->dma_dev->dev_id中。

1.5.2 在DMA完全准备好之前,已经有客户端在等待channel时,遍历所有的channel,
调用dma_chan_get(chan)

1.5.3 list_add_tail_rcu(&device->global_node, &dma_device_list) //将& dma_device. global_node加入静态链表中。

1.5.4 dma_channel_rebalance

1.5.4.1 chan->device->device_alloc_chan_resources(chan); <=> sunxi_alloc_chan_resources //无实际作用,最后返回0

1.5.4.2 balance_ref_count

1.6最后调用sunxi_dma_hw_init(sunxi_dev);使能时钟, -> clk_prepare_enable(sunxi_dev->ahb_clk);

2.2 数据结构关系图

dma_device结构体集成DMA控制器的核心函数,实现最底层的具体操作; 新建立的sunxi_chan对应每个通道,该结构体一面存放用户配置的cfg成员,另外就是包含virt_dma_chan,它用virt-dma提供的函数初始,并将vc->chan.device_node链接到dma_device结构体的channels链表中,后续实际的DMA通道的申请就是申请vc->chan通道,而vc->chan在内核中建立相应的文件节点,即chan->dev,生成的节点如下:

/sys/devices/platform/sunxi_dmac/dma/dma0chan0

在准备一次传输时,e.g chan->device->device_prep_dma_sg 即调用sunxi_prep_dma_sg,创建sunxi_desc结构体并初始化,该结构体可以理解为通道中具体运载工具,在传输完成后销毁。

在不同的传输阶段,会将Virt_dma_desc.node链入不同的virt_dma_chan链表中,如desc_submitted,desc_issued,desc_completed。

其他模块在调用dmaengine提供的接口来使用DMA时,是通过遍历&dma_device_list链表找到相应的dma
device。

在sunxi_start_desc函数中,会将txd->lli_phys写入DMA寄存器中,之前设置的相应参数,如slave_id,
起始地址等,将由DMA控制器来解析,选择等。

2.3 对外接口函数

include/linux/ dmaengine.h

#define dma_request_channel(mask, x, y) __dma_request_channel(&(mask), x, y)

struct dma_chan *__dma_request_channel(dma_cap_mask_t *mask, dma_filter_fn fn, void *fn_param) // mask,所有申请的传输类型的掩码;fn, DMA驱动私有的过滤函数;fn_param,
传入的私有参数

1.从dma_device_list全局链表中找到可用的dma_device

2. private_candidate(mask, device, fn, fn_param);//根据mask判断device是否符合要求,在满足条件的情况下,a.如果所有的通道都是公有的,在没有用户使用的情况下执行b,否则冲突,返回NULL;b.遍历dam_device.channels链表中的dma_chan,找到第一个client_count为0的chan.
具体代码如下:

static struct dma_chan *private_candidate(dma_cap_mask_t *mask, struct dma_device *dev,
					  dma_filter_fn fn, void *fn_param)
{
	struct dma_chan *chan;

	if (!__dma_device_satisfies_mask(dev, mask)) {
		pr_debug("%s: wrong capabilities\n", __func__);
		return NULL;
	}
	/* devices with multiple channels need special handling as we need to
	 * ensure that all channels are either private or public.
	 */
	if (dev->chancnt > 1 && !dma_has_cap(DMA_PRIVATE, dev->cap_mask))
		list_for_each_entry(chan, &dev->channels, device_node) {
			/* some channels are already publicly allocated */
			if (chan->client_count)
				return NULL;
		}

	list_for_each_entry(chan, &dev->channels, device_node) {
		if (chan->client_count) {
			pr_debug("%s: %s busy\n",
				 __func__, dma_chan_name(chan));
			continue;
		}
		if (fn && !fn(chan, fn_param)) {
			pr_debug("%s: %s filter said false\n",
				 __func__, dma_chan_name(chan));
			continue;
		}
		return chan;
	}

	return NULL;
}

3.找到合适的chan之后,

dma_cap_set(DMA_PRIVATE, device->cap_mask);// to disable balance_ref_count as this channel will not be published in the general-purpose allocator

device->privatecnt++;

err = dma_chan_get(chan);//a.获取dma channel’s parent driver模块,即该模块计数加1;

b. int desc_cnt = chan->device->device_alloc_chan_resources(chan);// <=> sunxi_alloc_chan_resources//设置schan->cyclic = false;

c. balance_ref_count(chan)//其目的是确保dma channel’s parent driver模块数与client数一致

include/linux/ dmaengine.h

static inline int dmaengine_slave_config(struct dma_chan *chan,struct dma_slave_config *config)

<=> dmaengine_device_control(chan, DMA_SLAVE_CONFIG,(unsigned long)config);

<=> chan->device->device_control(chan, cmd, arg);

<=>  sunxi_control <=> sunxi_set_runtime_config(chan, (struct dma_slave_config *)arg); //最终将用户配置的相关参数存储到sunxi_chan.cfg中。

static inline 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_transfer_direction dir,

unsigned long flags)

{

return chan->device->device_prep_dma_cyclic(chan, buf_addr, buf_len,period_len, dir, flags, NULL);

}

dmaengine_submit (struct dma_async_tx_descriptor *desc)

<=> drivers/dma/ virt-dma.c : vchan_tx_submit

cookie = dma_cookie_assign(tx);//递增cookie

list_add_tail(&vd->node, &vc->desc_submitted);//加入desc_submitted队列

static inline void dma_async_issue_pending(struct dma_chan *chan)

{

chan->device->device_issue_pending(chan);

}

<=> sunxi_issue_pending

a.vchan_issue_pending(&schan->vc)  ->

list_splice_tail_init(&vc->desc_submitted, &vc->desc_issued);

__list_splice(list, head->prev, head);//

INIT_LIST_HEAD(list);//清空list

b. if (list_empty(&schan->node))

list_add_tail(&schan->node, &sdev->pending);//将schan->node加入链表sdev->pending

c. tasklet_schedule(&sdev->task);//调度sdev->task,执行sunxi_dma_tasklet ->

sunxi_start_desc // virt_dma_desc结构变量vd为空时停止本次DMA传输;否则,设置对应channel的中断号,DMA操作模式,将txd->lli_phys写入寄存器,正式DMA传输。

drivers/dma/ dmaengine.c

void dma_release_channel(struct dma_chan *chan)

1.dma_chan_put

chan->client_count--;

module_put(dma_chan_to_owner(chan));

if (chan->client_count == 0)

chan->device->device_free_chan_resources(chan); <=> sunxi_free_chan_resources ->

vchan_free_chan_resources: 获取所有的desc_submitted、desc_issued、desc_completed descriptors之后,调用vchan_dma_desc_free_list(vc, &head):

while (!list_empty(head)) {

struct virt_dma_desc *vd = list_first_entry(head,struct virt_dma_desc, node);

list_del(&vd->node);

dev_dbg(vc->chan.device->dev, "txd %p: freeing\n", vd);

vc->desc_free(vd); // <=> sunxi_free_desc,释放virt_dma_desc

}

2.判断--chan->device->privatecnt为0时,

dma_cap_clear(DMA_PRIVATE, chan->device->cap_mask)

3 使用流程

注意事项:

回调函数里不允许休眠,以及调度;

回调函数时间不宜过长;

Pending并不是立即传输而是等待软中断的到来,cyclic模式除外;

在dma_slave_config中的slave_id对于devices必须要指定.

4 实例分析

drivers/char/dma_test/ sunxi_dma_test.c

创建sunxi_dma_test类及其属性文件test、help

当向test输入0时,即调用dma_test_main(0) -> case_memcpy_single_chan()

1.buf_group *buffers = NULL;  buffers = init_buf();//分配好内存

typedef struct {

unsigned int src_va;

unsigned int src_pa;

unsigned int dst_va;

unsigned int dst_pa;

unsigned int size;

}buf_item;

typedef struct {

unsigned int cnt;

buf_item item[BUF_MAX_CNT];

}buf_group;

2. chan = dma_request_channel(mask , NULL , NULL); //根据mask申请一个可用的通道

3. sg_alloc_table(&src_sg_table, buffers->cnt, GFP_KERNEL) // Allocate and initialize an sg table

使用scatterlist的原因就是系统在运行的时候内存会产生很多碎片,比如4k,100k的,1M的,有时候对应磁盘碎片,总之就是碎片。而在网络
和磁盘操作中很多时候需要传送大块的数据,尤其是使用DMA的时候,因为DMA操作的物理地址必须是连续的。假设要1M内存,此时可以分配一个整的1M内
存,也可以把10个10K的和9个100K的组成一块1M的内存,当然这19个块可能是不连续的,也可能其中某些或全部是连续的,总之情况不定,为了描述
这种情况,就引入了scatterlist,其实看成一个关于内存块构成的链表就OK了。

sg_set_buf(sg, phys_to_virt(buffers->item[i].src_pa), buffers->item[i].size);

sg_dma_address(sg) = buffers->item[i].src_pa;

4. dmaengine_slave_config(chan , &config); //配置参数,如传输方向,slave_id等。

5. tx = chan->device->device_prep_dma_sg(chan, dst_sg_table.sgl, buffers->cnt,

src_sg_table.sgl, buffers->cnt, DMA_PREP_INTERRUPT | DMA_CTRL_ACK);

准备一次多包传输,散列形式,返回一个传输描述符指针。

<=> sunxi_prep_dma_sg: //

a. vchan_tx_prep,初始化virt_dma_desc;

b. sunxi_alloc_lli//调用dma_pool_alloc从sunxi_dmadev.lli_pool内存块池分配内存。

c. sunxi_cfg_lli //配置sunxi_dma_lli相应参数,如flag,源地址,目的地址,数据长度等。

6.设置回调函数

dma_info.chan = chan;

init_waitqueue_head(&dma_info.dma_wq);

atomic_set(&dma_info.dma_done, 0);

tx->callback = __dma_callback; //唤醒中断;设置pinfo->dma_done为1。

tx->callback_param = &dma_info;

7.加入传输队列, cookie = dmaengine_submit(tx);

8.开始传输,dma_async_issue_pending(chan);

9.等待传输结束,ret = wait_event_interruptible_timeout(dma_info.dma_wq,atomic_read(&dma_info.dma_done)==1, timeout);

10.DMA传输完成后,产生中断,其中断处理函数为sunxi_dma_interrupt,

ch->desc = NULL;

vchan_cookie_complete(&desc->vd);

sunxi_start_desc(ch);// vchan_cookie_complete会释放virt_dma_desc,故会正常退出此次DMA传输。

vchan_cookie_complete解析:

a. dma_cookie_complete(&vd->tx);//设置cookie

b. list_add_tail(&vd->node, &vc->desc_completed);

c. tasklet_schedule(&vc->task);  -> vchan_complete

list_splice_tail_init(&vc->desc_completed, &head);

list_del(&vd->node);

vc->desc_free(vd); ó sunxi_free_desc //释放virt_dma_desc

if (cb)

cb(cb_data);//执行回调函数

11. dma_release_channel(chan);

时间: 2024-08-11 07:40:49

全志H3平台DMA框架的相关文章

全志H3平台CLOCK简析

1 概要 时钟管理模块是linux系统为统一管理各硬件的时钟而实现管理框架,负责所有模块的时钟调节和电源管理. 1.1 模块功能介绍 时钟管理模块主要负责处理各硬件模块的工作频率调节及电源切换管理.一个硬件模块要正常工作,必须先配置好硬件的工作频率.打开电源开关.总线访问开关等操作,时钟管理模块为设备驱动提供统一的操作接口,使驱动不用关心时钟硬件实现的具体细节. 1.2 相关术语介绍 晶振:晶体振荡器的简称,晶振有固定的振荡频率,如32K/24Mhz等,是芯片所有时钟的源头. PLL: 锁相环,

(转)android平台phonegap框架实现原理

(原文)http://blog.csdn.net/wuruixn/article/details/7405175 android平台phonegap框架实现原理 分类: Android2012-03-28 23:10 2919人阅读 评论(0) 收藏 举报 phonegap平台android框架apiblackberry 最近研究了下phonegap手机快速开发框架原理,重点探究了android平台上的phonegap框架源码,在参考cutesource写的phonegap源码分析后,更加深入理

开发指南专题二:JEECG微云高速开发平台JEECG框架初探

开发指南专题二:JEECG微云高速开发平台JEECG框架初探 2.JEECG框架初探 2.1演示系统 打开浏览器输入JEECG演示环境界址:http://demo.jeecg.org:8090/能够看到如图21所看到的的登录界面., 图21演示系统登录界面 点击[登陆]button,进入演示系统的主界面,如图22所看到的. 图22演示系统主界面 在JEECG演示系统中的功能模块包含系统管理.流程管理.业务申请.业务办理.经常使用功能演示等.当中,用户管理.流程设计器的界面截图如图23和图24所看

开发指南专题二:JEECG微云快速开发平台JEECG框架初探

开发指南专题二:JEECG微云快速开发平台JEECG框架初探 2.JEECG框架初探 2.1演示系统 打开浏览器输入JEECG演示环境地址:http://demo.jeecg.org:8090/可以看到如图21所示的登录界面., 图21演示系统登录界面 点击[登陆]按钮,进入演示系统的主界面,如图22所示. 图22演示系统主界面 在JEECG演示系统中的功能模块包括系统管理.流程管理.业务申请.业务办理.常用功能演示等.其中,用户管理.流程设计器的界面截图如图23和图24所示. 图2

[转]新兵训练营系列课程——平台RPC框架介绍

原文:http://weibo.com/p/1001643875439147097368 课程大纲 1.RPC简介 1.1 什么是RPC 1.2 RPC与其他远程调用方式比较 2.Motan RPC框架 2.1 RPC服务框架 2.2 Motan RPC框架中的角色 2.3 Motan RPC的调用流程 3.Motan RPC中各角色介绍 3.1 注册中心 3.2 RPC Service 3.3 RPC Client 4.Motan实战 4.1 Motan的使用方式 4.2 使用Motan提供R

MAC中在eclipse luna上搭建移动平台自动化测试框架(UIAutomator/Appium/Robotium/MonkeyRunner)关键点记录

这几天因为原来在用的hp laptop的电池坏掉了,机器一不小心就断电,所以只能花时间在自己的mackbook pro上重新搭建整套环境,大家都知道搭建环境是个很琐碎需要耐心的事情,特别是当你搭建的安卓平台的时候经常需要翻墙,那个慢不是常人可以忍受的,所以过程中建议大家边看书或者玩手机边搭建,省得一直瞪着屏幕导致爆血管的意外发生. 这里本人尝试把在mac上搭建移动平台自动化测试框架的一些碰到的问题和关键点给描述一下,以方便后来者可以借鉴. 1. 如果你需要的是最新的eclise,那么不要去and

20171128客户在全志R16平台的问题合集

客户在全志R16平台的问题合集20171128 2017/10/19 16:41 版本:V1.0 W工,您好! 问题1: 我是XXXXXX科技有限公司的陈工,在调试贵公司的R16开发板(系统为安卓4.4)的LVDS屏的时,你们的屏幕是1280x800,18位的屏,我们的是(分辨率为)1024x600.18位的屏,我修改了下面的信息,但是开机后整体画面有偏移,画面偏左而且没有占满整个屏(附件图中红框部分属于屏幕正常显示区域),下方的几个虚拟按键也没有显示出来.我修改行后沿时间跟场后沿时间并没有效果

全志A33平台编译linux(分色排版)sina33

全志A33平台编译linux 大文实验室/大文哥 壹捌陆捌零陆捌捌陆捌贰 21504965 AT qq.com 完成时间:2017/12/12 17:36 版本:V1.0 Xshell 5 (Build 0964) Copyright (c) 2002-2016 NetSarang Computer, Inc. All rights reserved. Type `help' to learn how to use Xshell prompt. [c:\~]$ Connecting to 192

【架构】技术-工具-平台-语言&amp;框架

技术-工具-平台-语言&框架 Techniques | Technology Radar | ThoughtWorks 原文地址:https://www.cnblogs.com/junneyang/p/8267546.html