- UID
- 1029342
- 性别
- 男
|
然后根据buf->write 这个DMA 读写标志来对callback 函数指针进行设置,是指向写DMA函数dma->write.callback,还是读DMA函数dma->read.callback。最后在调用该函数指针所指的函数时将buf->id,buf->size这两个值作为参数传入,即是原来定义在dma_irq_handler 函数中的dma变量的dma->curr->id和dma->curr->size,分别表示当前DMA 缓冲区的id号和缓冲区大小。
现在可以先来看一下DMA写入中断处理函数audio_dmaout_done_callback:
static void audio_dmaout_done_callback(void *buf_id, intsize)
audio_buf_t *b =(audio_buf_t *) buf_id;
在该函数中首先就定义了一个audio_buf_t 结构的指针变量,并指向传入的参数。
up(&b->sem);
up函数在这里表示释放信号量,关于该函数和另一个down 函数的具体细节会在后面说明。
wake_up(&b->sem.wait);
最后调用wake_up函数来唤醒所有在等待该信号量的进程。对于该函数的说明可以参考一篇《关于linux内核中等待队列的问题》的文档。
在/kernel/include/linux/sched.h文件中:
#definewake_up(x) __wake_up((x),TASK_UNINTERRUPTIBLE |TASK_INTERRUPTIBLE, 1)
该宏函数定义为__wake_up函数,参数TASK_INTERRUPTIBLE 为1,TASK_UNINTERRUPTIBLE为2,两者相或,表示将wait_queue list 中 process->state是TASK_INTERRUPTIBLE 或TASK_UNINTERRUPTIBLE 的所有进程叫醒。
在/kernel/kernel/sched.c文件中:
void __wake_up(wait_queue_head_t *q, unsigned int mode, intnr)
{
if (q) {
unsigned long flags;
wq_read_lock_irqsave(&q->lock,flags);
__wake_up_common(q, mode, nr,0);
wq_read_unlock_irqrestore(&q->lock,flags);
}
}
宏函数wq_read_lock_irqsave的作用主要就是保存IRQ 和FIQ 的中断使能状态,并禁止IRQ 中断;而宏函数wq_read_unlock_irqrestore的作用就是恢复IRQ 和FIQ 的中断使能状态。现在可以得知__wake_up 这个函数的作用,它首先保存IRQ 和FIQ的中断使能状态,并禁止IRQ 中断,接着调用__wake_up_common 函数来唤醒等待q 队列的进程,最后再恢复IRQ 和FIQ的中断使能状态。
/
static inline void up(struct semaphore * sem)
{
#if WAITQUEUE_DEBUG
CHECK_MAGIC(sem->__magic);
#endif
__up_op(sem,__up_wakeup);
}
在/kernel/include/asm-arm/proc-armo/locks.h 文件中:
#define__up_op(ptr,wake) \
({ \
__asm__ __volatile__( \
"@up_op\n" \
" mov ip,pc\n" \
" orr lr, ip,#0x08000000\n" \
" teqp lr,#0\n" \
" ldr lr,[%0]\n" \
" and ip, ip,#0x0c000003\n" \
" adds lr, lr,#1\n" \
" str lr,[%0]\n" \
" orrle ip, ip,#0x80000000 @ set N - should this be mi ??? DAG !\n" \
" teqp ip,#0\n" \
" movmi ip,%0\n" \
" blmi "SYMBOL_NAME_STR(wake) \
: \
: "r"(ptr) \
: "ip", "lr","cc"); \
})
用ARM 汇编指令完成对信号量加一计数后,调用了wake为标号的子程序,即传入的参数__up_wakeup 标号所在的子程序。
在/kernel/arch/arm/kernel/semaphore.c 文件中:
__up_wakeup: \n\
stmfd sp!, {r0 - r3,lr} \n\
mov r0,ip \n\
bl __up \n\
ldmfd sp!, {r0 - r3,pc}^ \n\
这里又调用了__up 函数。
void __up(struct semaphore*sem)
{
wake_up(&sem->wait);
}
最后在该函数中调用了wake_up函数来唤醒所有等待信号量的进程,wake_up 函数在上面已经有过说明。
如果这样的话,就有一个问题,在上面的audio_dmaout_done_callback函数中,先后调用了这两个函数:
up(&b->sem);
wake_up(&b->sem.wait);
其实在up 函数中也调用了wake_up 函数,这样不是重复调用了wake_up 函数嘛,不知道为什么。
------------------------------------------------------------------------
再来看一下DMA读取中断处理函数audio_dmain_done_callback:
static void audio_dmain_done_callback(void *buf_id, int size)
audio_buf_t *b =(audio_buf_t *) buf_id;
在该函数中首先就定义了一个audio_buf_t 结构的指针变量,并指向传入的参数。
b->size = size;
将b->size 赋值为传入的参数,即当前缓冲区的大小。
up(&b->sem);
wake_up(&b->sem.wait);
这两步和DMA写入中断处理函数一样,调用up 函数释放信号量,然后再调用wake_up 函数来唤醒所有在等待该信号量的进程。
------------------------------------------------------------------------
继续来看一下释放设备函数smdk2410_audio_release:
static int smdk2410_audio_release(struct inode *inode, struct file*file)
if(file->f_mode & FMODE_READ){
if (audio_rd_refcount == 1)
audio_clear_buf(&input_stream);
audio_rd_refcount = 0;
}
该函数中,首先根据file->f_mode判断文件是否可读,若为读取模式,则继续根据变量audio_rd_refcount来判断,若已经用读取模式打开过该设备文件,则调用audio_clear_buf 函数来清空输入音频DMA缓冲区,接着把audio_rd_refcount 这个读占位标志清零。
if(file->f_mode &FMODE_WRITE) {
if (audio_wr_refcount == 1) {
audio_sync(file);
audio_clear_buf(&output_stream);
audio_wr_refcount = 0;
}
}
接着再根据file->f_mode判断文件是否可写,若为写入模式,则继续根据变量audio_wr_refcount来判断,若已经用写入模式打开过该设备文件,则先调用audio_sync函数来保存内存数据到flash,该函数会在后面说明。然后再调用audio_clear_buf 函数来清空输出音频DMA缓冲区,接着把audio_wr_refcount 这个写占位标志清零。
MOD_DEC_USE_COUNT;
最后调用MOD_DEC_USE_COUNT; 来对设备文件计数器减一计数,并返回。
------------------------------------------------------------------------
下面来仔细分析一下写设备文件函数smdk2410_audio_write,在该函数中创建了DMA 缓冲区,并对DMA缓冲区进行了写入的操作,函数原型如下:
static ssize_t smdk2410_audio_write(struct file *file, const char*buffer,
size_t count, loff_t * ppos)
audio_stream_t *s= &output_stream;
该函数首先又定义了一个audio_stream_t 结构的指针变量指向输出音频缓冲区。
switch(file->f_flags & O_ACCMODE){
caseO_WRONLY:
caseO_RDWR:
break;
default:
return -EPERM;
}
然后根据file->f_flags这个表示设备文件的打开方式是读取,写入,还是可读写的标志进行判断,若为写入或可读写则继续执行,否则就会返回退出。
if(!s->buffers &&audio_setup_buf(s))
return -ENOMEM;
这里通过s->buffers 指针是否为空来判断有没有创建过DMA缓冲区。若s->buffers 指针不为空,则表示已经创建过DMA缓冲区,那么就不会执行audio_setup_buf 函数了;若s->buffers指针为空,则就会执行audio_setup_buf 函数来创建DMA缓冲区,创建成功的话就会返回0,这样就会继续执行下面的代码。该函数会在后面说明。
count&= ~0x03;
由于DMA数据必须4字节对齐传输,即每次传输4个字节,因此驱动程序需要保证每次写入的数据都是4的倍数。这样屏蔽掉所要写入字节数的最后2位就是4的倍数了。
while (count> 0) {
若要写入的字节数大于0,则进入一个while 大循环。
audio_buf_t *b =s->buf;
在大循环一开始就定义了一个audio_buf_t 结构的指针变量指向前面定义的输出音频缓冲区里的当前缓冲区指针。
if(file->f_flags & O_NONBLOCK){
ret =-EAGAIN;
if(down_trylock(&b->sem))
break;
} else {
ret =-ERESTARTSYS;
if(down_interruptible(&b->sem))
break;
}
然后根据file->f_flags 与上O_NONBLOCK 值来进行判断。O_NONBLOCK值表示采用非阻塞的文件IO方法,如果O_NONBLOCK标记被设置,文件描述符将不被阻塞而被直接返回替代。一个例子是打开tty。如果用户不在终端调用里输入任何东西,read将被阻塞,直到用户有输入,当O_NONBLOCK 标记被设置,read 调用将直接返回设置到EAGAIN 的值。
这里若应用程序在调用write 函数时加入了O_NONBLOCK 参数,则会调用down_trylock函数来试着获得信号量sem,如果能够立刻获得,它就获得该信号量并返回0,否则,表示不能获得信号量sem,返回值为非0值。该函数与相关函数在一篇《Linux内核的同步机制》中有详细说明。
在/kernel/include/asm-arm/semaphore.h 文件中:
static inline int down_trylock(struct semaphore *sem)
{
#if WAITQUEUE_DEBUG
CHECK_MAGIC(sem->__magic);
#endif
return__down_op_ret(sem, __down_trylock_failed);
}
在/kernel/include/asm-arm/proc-armo/locks.h 文件中:
#define__down_op_ret(ptr,fail) \
({ \
unsigned intresult; \
__asm__ __volatile__( \
" @down_op_ret\n" \
" mov ip,pc\n" \
" orr lr, ip,#0x08000000\n" \
" teqp lr,#0\n" \
" ldr lr,[%1]\n" \
" and ip, ip,#0x0c000003\n" \
" subs lr, lr,#1\n" \
" str lr,[%1]\n" \
" orrmi ip, ip,#0x80000000 @ set N\n" \
" teqp ip,#0\n" \
" movmi ip,%1\n" \
" movpl ip,#0\n" \
" blmi " SYMBOL_NAME_STR(fail)"\n" \
" mov %0,ip" \
: "=&r"(result) \
: "r"(ptr) \
: "ip", "lr","cc"); \
result; \
}) |
|