分类: LINUX
2011-01-27 16:03:03
DMA
谨以此文纪念过往的岁月。
DMA传输支持4种格式,内存到内存,设备到内存,内存到设备,设备到设备。
对于内存到内存比较好理解,就是不通过CPU的复制,直接使用进行数据传输。
1.dma的初始化
在cpu.c文件中会对CPU的一些最最基本的资源初始化,如时钟,中断等等,在该文件中会注册一个s3c6410_sysclass类,
struct sysdev_class s3c6410_sysclass = {
.name = "s3c6410-core",
};
对于搞驱动的人来说这个很不陌生,在创建一个设备前一般会去注册一个类的。
static int __init s3c6410_core_init(void)
{
return sysdev_class_register(&s3c6410_sysclass);
}
这个会在内核启动时初始化的,因为他被这样声明了core_initcall(s3c6410_core_init);在内核中像arch_initcall和core_initcall以及module_init,这些都会在
内核启动的时候初始化。会有先后顺序的,core_initcall在内核启动后就会初始化,之后是arch_initcall再之后才是module_init。
在注册该类后,dma的驱动以及dma的设备都可以挂在该类下了。
在arch/arm/mach-s3c6410/dma.c中会将dma_driver挂在s3c6410_sysclass下
static int __init s3c6410_dma_init(void)
{
return sysdev_driver_register(&s3c6410_sysclass, &s3c6410_dma_driver);
}
arch_initcall(s3c6410_dma_init);
对于s3c6410_dma_driver中,其实最主要的是其中两个函数s3c_dma_init,和s3c_dma_init_map。
static struct sysdev_driver s3c6410_dma_driver = {
.add = s3c6410_dma_add,
};
static int __init s3c6410_dma_add(struct sys_device *sysdev)
{
s3c_dma_init(S3C_DMA_CHANNELS, IRQ_DMA0, 0x20); --S3C_DMA_CHANNELS = 4*8 应为pl080有四个dma控制器,每一个有8个通道。
return s3c_dma_init_map(&s3c6410_dma_sel);
}
s3c_dma_init顾名思义是DMA的初始化,s3c_dma_init_map则是将dma通道与硬件匹配。
s3c_dma_init的源码在dma-pl080.c中,其具体的可以去参考源码,在其中主要是对dma的控制器以及每个通道的寄存器等等进行初始化。
其中有一个需要注意的是,为DMA开辟一个cache。dma_kmem是一个全局变量。
dma_kmem = kmem_cache_create("dma_desc", sizeof(struct s3c_dma_buf), 0,SLAB_HWCACHE_ALIGN, (void *)s3c_dma_cache_ctor);
s3c_dma_init_map的源码也在dma-pl080.c中,其具体的可以去参考源码.
2.就从内存到内存开始,以从内存开辟开始。
在linux的内存开辟中有kmalloc,vmalloc,dma_alloc_coherent等办法。kmalloc开辟连续的逻辑地址,vmalloc不连续,dma_alloc_coherent则开辟内存连续并且
一致的内存。
dma_alloc_coherent 原型:
void *dma_alloc_coherent(struct device *dev, size_t size, dma_addr_t *handle, gfp_t gfp)
dma_alloc_coherent 应用如下:
dmabuf = (unsigned char *)dma_alloc_coherent(NULL,BUFF_SIZE,(dma_addr_t *)&dmaphys,GFP_KERNEL);
dmabuf返回的是内核虚拟地址,BUFF_SIZE为分配的地址空间大小,dmaphys为总线地址,在ARM框架下可以理解为物理地址。
在分配内存空间后,即是申请DMA通道。s3c2410_dma_request的源码在dma-pl080.c中,在此我们不去详细的分析每一个代码,主要是分析代码的架构。以下有很多函数都是
在dma-pl080.c中,以后不会再说明函数的出处,除非特殊情况。
2.1 s3c2410_dma_request
int s3c2410_dma_request(unsigned int channel,struct s3c2410_dma_client *client,void *dev) 第二个参数是dma的名称。区别于其他的。
在s3c2410_dma_request中有两个比较重要的函数s3c_dma_map_channel 和s3c_enable_dmac。
s3c_dma_map_channel将虚拟的通道与真正的并且没有使用的通道相匹配。
原型为struct s3c2410_dma_chan *s3c_dma_map_channel(int channel),该函数会返回真正dma通道的参数。
在上面初始化的时候,没有将s3c_dma_init_map这个函数描述,在这儿描述一下。
s3c_dma_init_map在查看源码的时候会发现,他仅仅是一个赋值函数,就是将s3c_dma_init_map传下来的参数赋值给该文件中的全局变量dma_sel。其实这个函数作用就是
一个传话筒的功能。只是将s3c6410_dma_sel这个参数的指针赋值给dma_sel.其实真正的信息仍然存储在s3c6410_dma_sel中。
简要的将下面的函数讲述一下,下面的源码做个删减,一部分的检测代码删去。
struct s3c2410_dma_chan *s3c_dma_map_channel(int channel)
{
struct s3c_dma_map *ch_map;
struct s3c2410_dma_chan *dmach;
int ch;
ch_map = dma_sel.map + channel; dma_sel.map就是s3c6410_dma_sel.map,也就是s3c6410_dma_mappings[]数组的首指针。在这里是为了查找虚拟通道所对应的
真正的通道。其中主要的是寻找一个没有使用的通道。
for (ch = 0; ch < dma_channels; ch++) {
if (!is_channel_valid(ch_map->channels[ch])) 在这个是根据s3c6410_dma_sel中的ch_map的中channel是否可用来判定的。因为有些是被固定设定在某一个控制器的,
甚至某些功能被设定在固定的通道上。这个需要去仔细阅读源码中s3c6410_dma_sel这个结构体数组。
continue;
if (s3c_dma_chans[ch].in_use == 0) { 不仅是该dma的通道可以支持该功能,还要该通道未被使用
pr_debug("mapped channel %d to %d\n", channel, ch);
break;
}
}
found:
dmach = &s3c_dma_chans[ch]; 这个是取出真正对用的dma通道的信息的首指针返回。s3c_dma_chans是个全局变量,在s3c_dma_init中被初始化了。
dma_chan_map[channel] = dmach; 同时在匹配数组中记录。
(dma_sel.select)(dmach, ch_map); 这个函数比较简单直接就是dmach->map = ch_map.同样是记录信息的。
return dmach;
}
在linux中,往往真正的信息只会被一个数据结构所记录。而其他需要使用该信息的均会使用指针的办法来实现该数据结构的信息存储和调用。这也是linux内核比较难理解的地方。
在上述函数中真正存储信息的有s3c_dma_chans和s3c6410_dma_sel,而其他结构均使用指针来指向这两个结构体。这样的方法很常见。
OK,在上述函数中查询到真正的dma通道。进行了虚拟通道与真实通道的匹配。
在 s3c2410_dma_request中还有一个比较重要的函数,s3c_enable_dmac,这个函数会在该控制器的中断没有被声明使用的情况下用的,因为8个通道公用一个中断
如果该控制器第一次被使用的话,需要申请中断和打开控制器。
2.2 int s3c2410_dma_set_buffdone_fn(dmach_t channel, s3c2410_dma_cbfn_t rtn)
设置中断完成后的回调函数。这个很简单。不叙述,会在s3c_dma_irq中讲述。该回调函数是如何被调用的。
2.3 int s3c2410_dma_devconfig(int channel,enum s3c2410_dmasrc source,int hwcfg,unsigned long devaddr)
该函数配置DMA的源/目的硬件类型和地址。
参数 devaddr为源地址。source为dma传输的类型。在该函数的实现中根据不同的传输类型配置不同的寄存器。
2.4 int s3c2410_dma_config(dmach_t channel,int xferunit,int dcon)
xferunit 传输之间的字节大小 dcon DCONx 寄存器的基值
2.5 int s3c2410_dma_setflags(dmach_t channel, unsigned int flags)
2.6 int s3c2410_dma_enqueue(unsigned int channel, void *id,dma_addr_t data, int size)
这个函数非常重要,dma的操作几乎都在这里面完成。
在DMA中dma运行状态有三态
enum s3c_dma_state {
S3C_DMA_IDLE, --DMA空闲
S3C_DMA_RUNNING, --DMA运行
S3C_DMA_PAUSED --DMA暂停
};
而buffer的加载则有五态
enum s3c_dma_loadst {
S3C_DMALOAD_NONE, --没有buffer加载,此时通道应无效
S3C_DMALOAD_1LOADED, --有一个buffer被加载,但是没有被DMA确认加载,一是有可能通道没有运行,二可能是dma认为此时加载太浪费,等一会儿
S3C_DMALOAD_1RUNNING, --buffer被确认运行了,但是还没有结束。
S3C_DMALOAD_1LOADED_1RUNNING, --当前有一个缓冲区等待被dma加载,一个在运行
};
在dma运行过程中,理解这几个状态很重要。
struct s3c2410_dma_chan
{ ...
/* buffer list and information */
struct s3c_dma_buf *curr; /* current dma buffer */
struct s3c_dma_buf *next; /* next buffer to load */
struct s3c_dma_buf *end; /* end of queue */
...
}
int s3c2410_dma_enqueue(unsigned int channel, void *id,dma_addr_t data, int size)
{
struct s3c2410_dma_chan *chan = lookup_dma_channel(channel);
struct s3c_dma_buf *buf;
unsigned long flags;
buf = kmem_cache_alloc(dma_kmem, GFP_ATOMIC); --在s3c_dma_init中dma_kmem = kmalloc_cache_create创建一个新的高速缓存对象。在这里调用kmem_cache_aloc
从中分配内存对象。关于这个以后在理解内存时候,好好的理解。
if (buf == NULL) {
return -ENOMEM;
}
buf->next = NULL;
buf->data = buf->ptr = data;
buf->size = size;
buf->id = id;
buf->magic = BUF_MAGIC;
local_irq_save(flags);
if (chan->curr == NULL) { --如果当前的缓冲区为NULL
chan->curr = buf; 当前DMA缓冲区设置为buf
chan->end = buf; 同时将队列未同样设为buf
chan->next = NULL;
}
else { 如果当前缓冲区不为NULL
if (chan->end == NULL) /* In case of flushing */
{}
else {
chan->end->next = buf; 将上一个buffer的next设置为buf ????
chan->end = buf;
}
}
/* if necessary, update the next buffer field */
if (chan->next == NULL)
chan->next = buf;
/* check to see if we can load a buffer */
if (chan->state == S3C_DMA_RUNNING) { --如果当前DMA状态为RUNNING,则等待加载
if (chan->load_state == S3C_DMALOAD_1LOADED && 1) { --如果有一个buffer以被加载,但是没有被确认。等待加载。
if (s3c_dma_waitforload(chan, __LINE__) == 0) {
local_irq_restore(flags); --如果加载超时返回错误值
return -EINVAL;
}
}
}
else if (chan->state == S3C_DMA_IDLE) { --如果通道空闲,并且被设置为自动启动传输,则直接开始传输
if (chan->flags & S3C2410_DMAF_AUTOSTART) {
s3c2410_dma_ctrl(channel, S3C2410_DMAOP_START);
}
else {
pr_debug("loading onto stopped channel\n"); --否则错误,在该dma中并没有实现暂停的功能
}
}
local_irq_restore(flags);
return 0;
}
如果将这几个函数分开理解比较困难,如果将中断处理函数加入一起比较好理解。
static int s3c_dma_waitforload(struct s3c2410_dma_chan *chan, int line)
{
int timeout = chan->load_timeout;
int took;
if (chan->load_state != S3C_DMALOAD_1LOADED) { --为什么还会检测load_state?因为中断随时都会发生,导致load_state的改变。
return 0;
}
if (chan->stats != NULL)
chan->stats->loads++; --跟新channel的加载次数
while (--timeout > 0) {
if ((dma_rdreg(chan->dma_con, S3C_DMAC_ENBLD_CHANNELS)) & (0x1 << chan->number)) { --检测DMACEnbldChns 寄存器,如果对应的通道被启动,则说明
前一个buffer被加载,并启动dma
took = chan->load_timeout - timeout; --跟新等待时间。
s3c_dma_stats_timeout(chan->stats, took);
switch (chan->load_state) {
case S3C_DMALOAD_1LOADED: --同时更改load_state
chan->load_state = S3C_DMALOAD_1RUNNING; --同时将状态该为运行态
break;
default:
dbg();
}
return 1; --如果在限定时间内加载则返回1
}
}
if (chan->stats != NULL) {
chan->stats->timeout_failed++; --超时,则更新通道状态。
}
return 0; --超时返回0
}
下面来看一下中断处理函数分析。
static irqreturn_t s3c_dma_irq(int irq, void *devpw)
{
unsigned int channel = 0, dcon_num, i;
unsigned long tmp;
s3c_dma_controller_t *dma_controller = (s3c_dma_controller_t *) devpw;
struct s3c2410_dma_chan *chan=NULL;
struct s3c_dma_buf *buf;
dcon_num = dma_controller->number;
tmp = dma_rdreg(dma_controller, S3C_DMAC_INT_TCSTATUS); --读取当前控制器的中断状态,每一个控制器都有8个通道,每一个通道对应中断状态寄存器的每一位
if(tmp==0) {
return IRQ_HANDLED;
}
for (i = 0; i < S3C_CHANNELS_PER_DMA; i++) {
if (tmp & 0x01) {
channel = i;
chan = &s3c_dma_chans[channel + dcon_num * S3C_CHANNELS_PER_DMA]; --获取产生中断的通道信息。
buf = chan->curr; --获取当前buffer的信息
/* modify the channel state */
switch (chan->load_state) { load_state状态的切换
case S3C_DMALOAD_1RUNNING: 将运行态切换成,没有加载没有运行
chan->load_state = S3C_DMALOAD_NONE;
break;
case S3C_DMALOAD_1LOADED: 将运行态切换成,没有加载没有运行,抛弃更改状态
chan->load_state = S3C_DMALOAD_NONE;
break;
case S3C_DMALOAD_1LOADED_1RUNNING: 一个加载一个运行,改为一个加载
chan->load_state = S3C_DMALOAD_1LOADED;
break;
case S3C_DMALOAD_NONE:
break;
default:
break;
}
if (buf != NULL) { --进行二次切换将下一个缓冲区前置
chan->curr = buf->next;
buf->next = NULL;
if (buf->magic != BUF_MAGIC) {
goto next_channel;
}
s3c_dma_buffdone(chan, buf, S3C2410_RES_OK); --回调函数,会回调s3c2410_dma_set_buffdone_fn中设置的函数
s3c_dma_freebuf(buf);
}
else {
}
if (chan->next != NULL) {
unsigned long flags;
switch (chan->load_state) {
case S3C_DMALOAD_1RUNNING:
break;
case S3C_DMALOAD_NONE:
break;
case S3C_DMALOAD_1LOADED:
if (s3c_dma_waitforload(chan, __LINE__) == 0) {
goto next_channel;
}
break;
case S3C_DMALOAD_1LOADED_1RUNNING:
goto next_channel;
default:
goto next_channel;
}
local_irq_save(flags);
s3c_dma_loadbuffer(chan, chan->next);
local_irq_restore(flags);
} else {
s3c_dma_lastxfer(chan);
if (chan->load_state == S3C_DMALOAD_NONE) {
s3c2410_dma_ctrl(chan->index | DMACH_LOW_LEVEL, S3C2410_DMAOP_STOP);
}
}
}
next_channel:
tmp >>= 1;
}
s3c_clear_interrupts(chan->dma_con->number, chan->number);
return IRQ_HANDLED;
}
其中主要是dma启动
static int s3c_dma_start(struct s3c2410_dma_chan *chan)
{
unsigned long flags;
local_irq_save(flags);
if (chan->state == S3C_DMA_RUNNING) {
local_irq_restore(flags);
return 0;
}
chan->state = S3C_DMA_RUNNING;
if (chan->load_state == S3C_DMALOAD_NONE) {
if (chan->next == NULL) {
chan->state = S3C_DMA_IDLE;
local_irq_restore(flags);
return -EINVAL;
}
s3c_dma_loadbuffer(chan, chan->next);
}
if (!chan->irq_enabled) {
enable_irq(chan->irq);
chan->irq_enabled = 1;
}
dma_wrreg(chan, S3C_DMAC_CxCONFIGURATION, chan->config_flags);
s3c_dma_call_op(chan, S3C2410_DMAOP_START);
local_irq_restore(flags);
return 0;
}
static inline int s3c_dma_loadbuffer(struct s3c2410_dma_chan *chan,struct s3c_dma_buf *buf)
{
writel(buf->data, chan->addr_reg);
dma_wrreg(chan, S3C_DMAC_CxCONTROL0, chan->dcon);
dma_wrreg(chan, S3C_DMAC_CxCONTROL1, (buf->size / chan->xfer_unit));
chan->next = buf->next;
switch (chan->load_state) {
case S3C_DMALOAD_NONE:
chan->load_state = S3C_DMALOAD_1LOADED;
break;
case S3C_DMALOAD_1RUNNING:
chan->load_state = S3C_DMALOAD_1LOADED_1RUNNING;
break;
default:
break;
}
return 0;
}