全部博文(584)
分类:
2010-08-19 16:18:35
同 时我们也不难推测 , 使用 DMA 的函数应该都在 Arch\arm\plat-s3c24xx\dma.c 下 . 没错 , 说的更具体些就是这个文件下被 EXPORT_SYMBOL 出来的函数都是提供给外部使用的 , 也就是其他部分使用 DMA 的接口 . 知道了这些我们接着来分析这些被 EXPORT_SYMBOL 的函数吧 .
Arch\arm\plat-s3c24xx\dma.c:
/* s3c2410_dma_getposition
*
* returns the current transfer points for the dma source and destination
*/
int s3c2410_dma_getposition(dmach_t channel, dma_addr_t *src, dma_addr_t *dst)
{
获取保存该 channel 信息的对象 , 初始化的时候讲过
struct s3c2410_dma_chan *chan = lookup_dma_channel(channel);
if (chan == NULL)
return -EINVAL;
if (src != NULL) // 获取源地址
*src = dma_rdreg(chan, S3C2410_DMA_DCSRC);
if (dst != NULL) // 获取目的地址
*dst = dma_rdreg(chan, S3C2410_DMA_DCDST);
return 0;
}
EXPORT_SYMBOL(s3c2410_dma_getposition);
这 个函数获取某个 channel 当前正在传输的源地址和目的地址 . 主要就是通过读该 channel 的源和目的寄存器获得的 . S3C2410_DMA_DCSRC, S3C2410_DMA_DCDST 就是源和目的的偏移地址 . 可参考 2410 的 datasheet. dma_rdreg 就是读寄存器 .
Arch\arm\plat-s3c24xx\dma.c:
#define dma_rdreg(chan, reg) readl((chan)->regs + (reg))
接着看下一个 export 的函数
/* s3c2410_dma_devconfig
*
* configure the dma source/destination hardware type and address
*
* source: S3C2410_DMASRC_HW: source is hardware
* S3C2410_DMASRC_MEM: source is memory
*
* hwcfg: the value for xxxSTCn register,
* bit 0: 0=increment pointer, 1=leave pointer
* bit 1: 0=source is AHB, 1=source is APB
*
* devaddr: physical address of the source
*/
Arch\arm\plat-s3c24xx\dma.c:
int s3c2410_dma_devconfig(int channel,
enum s3c2410_dmasrc source,
int hwcfg,
unsigned long devaddr)
{
// 获取保存该 channel 信息的对象 , 初始化的时候讲过
struct s3c2410_dma_chan *chan = lookup_dma_channel(channel);
if (chan == NULL)
return -EINVAL;
pr_debug("%s: source=%d, hwcfg=%08x, devaddr=%08lx\n",
__FUNCTION__, (int)source, hwcfg, devaddr);
chan->source = source; // 保存 DMA 源
chan->dev_addr = devaddr; // 保存源地址
// 根据不同的 DMA 源来初始化 DMA channel
switch (source) {
case S3C2410_DMASRC_HW:
/* source is hardware */
pr_debug("%s: hw source, devaddr=%08lx, hwcfg=%d\n",
__FUNCTION__, devaddr, hwcfg);
dma_wrreg(chan, S3C2410_DMA_DISRCC, hwcfg & 3);
dma_wrreg(chan, S3C2410_DMA_DISRC, devaddr); // 源地址
dma_wrreg(chan, S3C2410_DMA_DIDSTC, (0<<1) | (0<<0));
chan->addr_reg = dma_regaddr(chan, S3C2410_DMA_DIDST);
return 0;
case S3C2410_DMASRC_MEM:
/* source is memory */
pr_debug( "%s: mem source, devaddr=%08lx, hwcfg=%d\n",
__FUNCTION__, devaddr, hwcfg);
dma_wrreg(chan, S3C2410_DMA_DISRCC, (0<<1) | (0<<0));
dma_wrreg(chan, S3C2410_DMA_DIDST, devaddr);
dma_wrreg(chan, S3C2410_DMA_DIDSTC, hwcfg & 3);
chan->addr_reg = dma_regaddr(chan, S3C2410_DMA_DISRC);
return 0;
}
printk(KERN_ERR "dma%d: invalid source type (%d)\n", channel, source);
return -EINVAL;
}
EXPORT_SYMBOL(s3c2410_dma_devconfig);
这个函数用来配置某个 channel 的源的类型及源地址 , 然后为某种源设置好地址增长方式 , 具体寄存器含义参考 2410 datasheet, 2410 下 DMA 的各种操作模式可参考我的另一篇文章 .
Arch\arm\plat-s3c24xx\dma.c:
int s3c2410_dma_set_buffdone_fn(dmach_t channel, s3c2410_dma_cbfn_t rtn)
{
struct s3c2410_dma_chan *chan = lookup_dma_channel(channel);
if (chan == NULL)
return -EINVAL;
pr_debug("%s: chan=%p, callback rtn=%p\n", __FUNCTION__, chan, rtn);
chan->callback_fn = rtn; // 设置回调函数
return 0;
}
EXPORT_SYMBOL(s3c2410_dma_set_buffdone_fn);
该函数主要为某个 channel 设置一个 done 的回调函数 . 该回调函数会在传输完成后被调用 .
Arch\arm\plat-s3c24xx\dma.c:
/* do we need to protect the settings of the fields from
* irq?
*/
int s3c2410_dma_set_opfn(dmach_t channel, s3c2410_dma_opfn_t rtn)
{
struct s3c2410_dma_chan *chan = lookup_dma_channel(channel);
if (chan == NULL)
return -EINVAL;
pr_debug("%s: chan=%p, op rtn=%p\n", __FUNCTION__, chan, rtn);
chan->op_fn = rtn;
return 0;
}
EXPORT_SYMBOL(s3c2410_dma_set_opfn);
该函数主要为某个 channel 设置一个操作的回调函数 . 该回调函数会在操作该 channel 时被调用 ( 有哪些操作会在 s3c2410_dma_ctrl 里看到 )
Arch\arm\plat-s3c24xx\dma.c:
int s3c2410_dma_setflags(dmach_t channel, unsigned int flags)
{
struct s3c2410_dma_chan *chan = lookup_dma_channel(channel);
if (chan == NULL)
return -EINVAL;
pr_debug("%s: chan=%p, flags=%08x\n", __FUNCTION__, chan, flags);
chan->flags = flags; // 设置标记
return 0;
}
EXPORT_SYMBOL(s3c2410_dma_setflags);
该函数主要为某个 channel 设置一个标记 , 标记有 :
Include\asm-arm\arch-s3c2410\dma.h:
/* flags */
#define S3C2410_DMAF_SLOW (1<<0) /* slow, so don't worry about
* waiting for reloads */
#define S3C2410_DMAF_AUTOSTART (1<<1) /* auto-start if buffer queued */
我们会在后面看到 flag 的使用
Arch\arm\plat-s3c24xx\dma.c:
/* DMA configuration for each channel
*
* DISRCC -> source of the DMA (AHB,APB)
* DISRC -> source address of the DMA
* DIDSTC -> destination of the DMA (AHB,APD)
* DIDST -> destination address of the DMA
*/
/* s3c2410_dma_config
*
* xfersize: size of unit in bytes (1,2,4)
* dcon: base value of the DCONx register
*/
int s3c2410_dma_config(dmach_t channel,
int xferunit,
int dcon)
{
struct s3c2410_dma_chan *chan = lookup_dma_channel(channel);
pr_debug("%s: chan=%d, xfer_unit=%d, dcon=%08x\n",
__FUNCTION__, channel, xferunit, dcon);
if (chan == NULL)
return -EINVAL;
pr_debug("%s: Initial dcon is %08x\n", __FUNCTION__, dcon);
dcon |= chan->dcon & dma_sel.dcon_mask;
pr_debug("%s: New dcon is %08x\n", __FUNCTION__, dcon);
// 设置每个传输单元的大小
switch (xferunit) {
case 1:
dcon |= S3C2410_DCON_BYTE;
break;
case 2:
dcon |= S3C2410_DCON_HALFWORD;
break;
case 4:
dcon |= S3C2410_DCON_WORD;
break;
default:
pr_debug("%s: bad transfer size %d\n", __FUNCTION__, xferunit);
return -EINVAL;
}
dcon |= S3C2410_DCON_HWTRIG; // 硬件请求模式
dcon |= S3C2410_DCON_INTREQ; // 打开中断
pr_debug("%s: dcon now %08x\n", __FUNCTION__, dcon);
// 保存配置到全局变量中
chan->dcon = dcon;
chan->xfer_unit = xferunit;
return 0;
}
EXPORT_SYMBOL(s3c2410_dma_config);
该函数主要用来配置某个 channel 的请求模式 , 传输单元大小等 . 从中可以看出目前只支持硬件请求模式
Arch\arm\plat-s3c24xx\dma.c:
int
s3c2410_dma_ctrl(dmach_t channel, enum s3c2410_chan_op op)
{
struct s3c2410_dma_chan *chan = lookup_dma_channel(channel);
if (chan == NULL)
return -EINVAL;
switch (op) {
case S3C2410_DMAOP_START:
return s3c2410_dma_start(chan); // 开始一个 DMA 传输
case S3C2410_DMAOP_STOP:
return s3c2410_dma_dostop(chan); // 停止一个 DMA 传输
case S3C2410_DMAOP_PAUSE:
case S3C2410_DMAOP_RESUME:
return -ENOENT;
case S3C2410_DMAOP_FLUSH:
return s3c2410_dma_flush(chan); //
case S3C2410_DMAOP_STARTED: // 指示传输开始
return s3c2410_dma_started(chan);
case S3C2410_DMAOP_TIMEOUT:
return 0;
}
return -ENOENT; /* unknown, don't bother */
}
EXPORT_SYMBOL(s3c2410_dma_ctrl);
OK, 这个函数主要就是用来启用 , 停止 DMA 操作了 , 比较重要的一个函数 . 等分析完了 export 的接口后 , 我们在来逐个分析每个 DMA 操作 .
Arch\arm\plat-s3c24xx\dma.c:
/* s3c2410_dma_free
*
* release the given channel back to the system, will stop and flush
* any outstanding transfers, and ensure the channel is ready for the
* next claimant.
*
* Note, although a warning is currently printed if the freeing client
* info is not the same as the registrant's client info, the free is still
* allowed to go through.
*/
int s3c2410_dma_free(dmach_t channel, struct s3c2410_dma_client *client)
{
struct s3c2410_dma_chan *chan = lookup_dma_channel(channel);
unsigned long flags;
if (chan == NULL)
return -EINVAL;
local_irq_save(flags);
if (chan->client != client) {
printk(KERN_WARNING "dma%d: possible free from different client (channel %p, passed %p)\n",
channel, chan->client, client);
}
/* sort out stopping and freeing the channel */
if (chan->state != S3C2410_DMA_IDLE) { // 该 channel 正在使用中
pr_debug("%s: need to stop dma channel %p\n",
__FUNCTION__, chan);
/* possibly flush the channel */
s3c2410_dma_ctrl(channel, S3C2410_DMAOP_STOP); // 停止该 channel
}
//reset 该 channel 的相关信息
chan->client = NULL;
chan->in_use = 0;
if (chan->irq_claimed)
free_irq(chan->irq, (void *)chan); // 释放该中断
chan->irq_claimed = 0;
if (!(channel & DMACH_LOW_LEVEL))
dma_chan_map[channel] = NULL;
local_irq_restore(flags);
return 0;
}
EXPORT_SYMBOL(s3c2410_dma_free);
根据注释我们很清楚了 , 该函数主要就是释放一个 channel, 使其处于 ready 状态 ,
Arch\arm\plat-s3c24xx\dma.c:
/* s3c2410_request_dma
*
* get control of an dma channel
*/
int s3c2410_dma_request(unsigned int channel,
struct s3c2410_dma_client *client,
void *dev)
{
struct s3c2410_dma_chan *chan;
unsigned long flags;
int err;
pr_debug("dma%d: s3c2410_request_dma: client=%s, dev=%p\n",
channel, client->name, dev);
local_irq_save(flags);
// 获取空闲的 channel
chan = s3c2410_dma_map_channel(channel);
if (chan == NULL) { // 无空闲 channel 则返回失败
local_irq_restore(flags);
return -EBUSY;
}
dbg_showchan(chan);
// 保存使用该 channel 的用户等信息
chan->client = client;
chan->in_use = 1;
if (!chan->irq_claimed) { // 该中断没注册
pr_debug("dma%d: %s : requesting irq %d\n",
channel, __FUNCTION__, chan->irq);
chan->irq_claimed = 1; // 标记注册
local_irq_restore(flags);
err = request_irq(chan->irq, s3c2410_dma_irq, IRQF_DISABLED,
client->name, (void *)chan); // 注册该中断
local_irq_save(flags);
if (err) { // 失败则 reset 该 channel
chan->in_use = 0;
chan->irq_claimed = 0;
local_irq_restore(flags);
printk(KERN_ERR "%s: cannot get IRQ %d for DMA %d\n",
client->name, chan->irq, chan->number);
return err;
}
chan->irq_enabled = 1;
}
local_irq_restore(flags);
/* need to setup */
pr_debug("%s: channel initialised, %p\n", __FUNCTION__, chan);
return 0;
}
EXPORT_SYMBOL(s3c2410_dma_request);
该函数主要就是为请求的用户找到一个空闲的 channel, 并把它分配给该用户 , 同时打开中断 , 保存相关信息 .
Arch\arm\plat-s3c24xx\dma.c:
/* s3c2410_dma_enqueue
*
* queue an given buffer for dma transfer.
*
* id the device driver's id information for this buffer
* data the physical address of the buffer data
* size the size of the buffer in bytes
*
* If the channel is not running, then the flag S3C2410_DMAF_AUTOSTART
* is checked, and if set, the channel is started. If this flag isn't set,
* then an error will be returned.
*
* It is possible to queue more than one DMA buffer onto a channel at
* once, and the code will deal with the re-loading of the next buffer
* when necessary.
*/
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 s3c2410_dma_buf *buf;
unsigned long flags;
if (chan == NULL)
return -EINVAL;
pr_debug("%s: id=%p, data=%08x, size=%d\n",
__FUNCTION__, id, (unsigned int)data, size);
// 从高速缓冲中分配一块 buffer 用于 DMA 传输 , dma_kmem 是我们在初始化的时候就创建好的
buf = kmem_cache_alloc(dma_kmem, GFP_ATOMIC);
if (buf == NULL) {
pr_debug("%s: out of memory (%ld alloc)\n",
__FUNCTION__, (long)sizeof(*buf));
return -ENOMEM;
}
//pr_debug("%s: new buffer %p\n", __FUNCTION__, buf);
//dbg_showchan(chan);
// 初始化这块要被传输的 buf
buf->next = NULL;
buf->data = buf->ptr = data; // 指向要传输的 data
buf->size = size; // 传输大小
buf->id = id;
buf->magic = BUF_MAGIC;
local_irq_save(flags);
if (chan->curr == NULL) { // 当前 channel 没有在传输
/* we've got nothing loaded... */
pr_debug("%s: buffer %p queued onto empty channel\n",
__FUNCTION__, buf);
chan->curr = buf; // 直接挂在 curr 上
chan->end = buf;
chan->next = NULL;
} else { // 当前 channel 正在传输
pr_debug("dma%d: %s: buffer %p queued onto non-empty channel\n",
chan->number, __FUNCTION__, buf);
if (chan->end == NULL)
pr_debug("dma%d: %s: %p not empty, and chan->end==NULL?\n",
chan->number, __FUNCTION__, chan);
// 把 buffer 挂到队列的最后面 , 并重设 end
chan->end->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 == S3C2410_DMA_RUNNING) { // 该 channel 正在运行
if (chan->load_state == S3C2410_DMALOAD_1LOADED && 1) { // 已有 buf load 了
if (s3c2410_dma_waitforload(chan, __LINE__) == 0) { // 等待 load
printk(KERN_ERR "dma%d: loadbuffer:"
"timeout loading buffer\n",
chan->number);
dbg_showchan(chan);
local_irq_restore(flags);
return -EINVAL;
}
}
while (s3c2410_dma_canload(chan) && chan->next != NULL) { // 检查能否 load
s3c2410_dma_loadbuffer(chan, chan->next); //load buffer
}
} else if (chan->state == S3C2410_DMA_IDLE) { // 该 channel 空闲着
if (chan->flags & S3C2410_DMAF_AUTOSTART) { // 如果设了自动启动标记 , 则直接启动该次传输
s3c2410_dma_ctrl(chan->number, S3C2410_DMAOP_START); // 启动传输
}
}
local_irq_restore(flags);
return 0;
}
EXPORT_SYMBOL(s3c2410_dma_enqueue);
该函数首先从先前创建的高速缓冲池中获取一个 buf, 并把要传输的 data 保存在该 buf 中 , 然后根据当前 channel 的运行状态来选择是 load 该 buf, 还是直接传输该 buf.