📄 armlinux-uda1341.c
字号:
static void audio_dmaout_done_callback(void *buf_id, int size)
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 文件中:
#define wake_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, int nr)
{
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 的中断使能状态。
//*******************************************************
//* 2007.7.10
//*******************************************************
// down()操作可以理解为申请资源,up()操作可以理解为释放资源,因此,信号量实际表示的是资源的数量以及是否有进程正在等待。
//在/kernel/include/asm-arm/semaphore.h 文件中:
struct semaphore {
atomic_t count;
int sleepers;
wait_queue_head_t wait;
#if WAITQUEUE_DEBUG
long __magic;
#endif
};
// 在semaphore 结构中,count 相当于资源计数,为正数或0 时表示可用资源数,-1 则表示没有空闲资源且有等待进程。而等待进程的数量并不关心。这种设计主要是考虑与信号量的原语相一致,当某个进程执行up 函数释放资源,点亮信号灯时,如果count 恢复到0,则表示尚有进程在等待该资源,因此执行唤醒操作。
// 一个典型的down()-up()流程是这样的:
//down()-->count做原子减1操作,如果结果不小于0则表示成功申请,从down()中返回;
// -->如果结果为负(实际上只可能是-1),则表示需要等待,则调用__down_fail();
// __down_fail()调用__down(),__down()用C代码实现,要求已不如down()和__down_fail()严格,在此作实际的等待。
//在/kernel/include/asm-arm/semaphore.h 文件中:
/*
* Note! This is subtle. We jump to wake people up only if
* the semaphore was negative (== somebody was waiting on it).
* The default case (no contention) will result in NO
* jumps for both down() and up().
*/
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) {
case O_WRONLY:
case O_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 int result; \
__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; \
})
//用ARM 汇编指令完成对信号量减一计数后,调用了fail 为标号的子程序,即传入的参数__down_trylock_failed 标号所在的子程序。
//在/kernel/arch/arm/kernel/semaphore.c 文件中:
__down_trylock_failed: \n\
stmfd sp!, {r0 - r3, lr} \n\
mov r0, ip \n\
bl __down_trylock \n\
mov ip, r0 \n\
ldmfd sp!, {r0 - r3, pc}^ \n\
//这里又调用了__down_trylock 函数。
/*
* Trylock failed - make sure we correct for
* having decremented the count.
*
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -