📄 armlinux-uda1341.c
字号:
* We could have done the trylock with a
* single "cmpxchg" without failure cases,
* but then it wouldn't work on a 386.
*/
int __down_trylock(struct semaphore * sem)
{
int sleepers;
unsigned long flags;
spin_lock_irqsave(&semaphore_lock, flags);
sleepers = sem->sleepers + 1;
sem->sleepers = 0;
/*
* Add "everybody else" and us into it. They aren't
* playing, because we own the spinlock.
*/
if (!atomic_add_negative(sleepers, &sem->count))
wake_up(&sem->wait);
spin_unlock_irqrestore(&semaphore_lock, flags);
return 1;
}
//这里不再进一步深入说明。
// 若应用程序在调用write 函数时没有加入了O_NONBLOCK 参数,即表示采用阻塞的文件IO方式,则会调用down_interruptible 函数来获得信号量sem。该函数将把sem 的值减1,如果信号量sem 的值非负,就直接返回,否则调用者将被挂起,直到别的任务释放该信号量才能继续运行。down_interruptible 函数能被信号打断,因此该函数有返回值来区分是正常返回还是被信号中断,如果返回0,表示获得信号量正常返回,如果被信号打断,返回-EINTR。该函数与相关函数在一篇《Linux内核的同步机制》中有详细说明。
//在/kernel/include/asm-arm/semaphore.h 文件中:
/*
* This is ugly, but we want the default case to fall through.
* "__down_interruptible" is the actual routine that waits...
*/
static inline int down_interruptible (struct semaphore * sem)
{
#if WAITQUEUE_DEBUG
CHECK_MAGIC(sem->__magic);
#endif
return __down_op_ret(sem, __down_interruptible_failed);
}
//函数__down_op_ret 在上面已经有过说明。
//在/kernel/arch/arm/kernel/semaphore.c 文件中:
__down_interruptible_failed: \n\
stmfd sp!, {r0 - r3, lr} \n\
mov r0, ip \n\
bl __down_interruptible \n\
mov ip, r0 \n\
ldmfd sp!, {r0 - r3, pc}^ \n\
//这里又调用了__down_interruptible 函数。
int __down_interruptible(struct semaphore * sem)
//这里不再进一步深入说明。
if (audio_channels == 2) {
chunksize = s->fragsize - b->size;
if (chunksize > count)
chunksize = count;
DPRINTK("write %d to %d\n", chunksize, s->buf_idx);
if (copy_from_user(b->start + b->size, buffer, chunksize)) {
up(&b->sem);
return -EFAULT;
}
b->size += chunksize;
}
// 下面继续对音频通道数量进行判断,如果音频通道数为先前打开设备文件时设的2通道,则进入执行。
//*******************************************************
//* 2007.7.11
//*******************************************************
// 对于“chunksize = s->fragsize - b->size”这一句一开始一直不太理解,
//不知道为什么要将音频缓冲区片大小减去DMA 缓冲区大小作为写入的数据长度,
//这两个量的大小是一样的,这样一减不是变为0 了吗?现在觉得其实b->size 只是一个缓冲区地址的偏移量,
//一开始这个偏移量应该为0,这样就不难理解用s->fragsize 作为写入的数据长度。
//接下去判断,如果所要写入的数据长度count 小于chunksize 值,那就以count 为准备写入数据的长度。
//在count 大于chunksize 的情况下,写入的数据长度以一个s->fragsize 大小为单位。
// 然后调用了copy_from_user 函数将用户空间buffer 里的数据复制到内核空间起始地址为b->start + b->size 的内存中,
//复制数据长度为chunksize。这里b->start 为指向环形缓冲区中第0个缓冲区地址的内存起始地址(虚拟地址),
//用这个起始地址加上缓冲区地址的偏移量(0)还是指向第0个缓冲区地址(共8个)的起始地址(虚拟地址)。
// 若copy_from_user 函数执行成功,则返回0,继续执行将缓冲区地址的偏移量b->size 加上已写入的数据长度chunksize。
//若copy_from_user 函数执行失败,就调用up 函数释放信号量,并退出写设备文件函数。
else {
chunksize = (s->fragsize - b->size) >> 1;
if (chunksize > count)
chunksize = count;
DPRINTK("write %d to %d\n", chunksize*2, s->buf_idx);
if (copy_from_user_mono_stereo(b->start + b->size,
buffer, chunksize)) {
up(&b->sem);
return -EFAULT;
}
b->size += chunksize*2;
}
// 如果音频通道数不等于先前打开设备文件时设的2通道,则进入执行。这里暂时先不进行分析,以后再来分析。
buffer += chunksize;
count -= chunksize;
// 当把一组音频缓冲区片大小的数据写入内存后,用户层的buffer 指针加上已写入数据的长度,即指向了下一组将要写入的数据。所要写入的数据长度count 减去已写入数据的长度,为还要写入数据的长度。
if (b->size < s->fragsize) {
up(&b->sem);
break;
}
// 若缓冲区地址的偏移量b->size 小于频缓冲区片大小,则调用up 函数释放信号量,并跳出while 大循环。但是一般情况不会进入该条件语句执行。
s3c2410_dma_queue_buffer(s->dma_ch, (void *) b,
b->dma_addr, b->size, DMA_BUF_WR);
// 该函数完成了管理DMA 缓冲区的相关数据结构s3c2410_dma_t 和dma_buf_t 进行了设置,
//并对S3C2410 芯片的DMA 控制器部分的相关寄存器进行了相应配置。
// 传入的参数为DMA 通道号,一个空指针,DMA 缓冲区的物理起始地址,
// DMA 缓冲区大小,DMA 缓冲区工作模式,这里工作模式为写DMA 缓冲区。
// 该函数原型在/kernel/arch/arm/mach-s3c2410/dma.c 文件中,会在后面专门进行分析。
b->size = 0;
NEXT_BUF(s, buf);
}
// 在while 大循环最后将缓冲区地址的偏移量b->size 清零,然后调用宏函数NEXT_BUF 来将当前缓冲区的指针指向环形缓冲区中下一个缓冲区地址处。所以在对DMA 缓冲区进行填写的前后,缓冲区地址的偏移量b->size 都为0。
// 接着如果要写入的数据长度count 还大于0,则继续在该循环中执行。
#define NEXT_BUF(_s_,_b_) { \
(_s_)->_b_##_idx++; \
(_s_)->_b_##_idx %= (_s_)->nbfrags; \
(_s_)->_b_ = (_s_)->buffers + (_s_)->_b_##_idx; }
//该宏函数相当与执行了一下语句:
s->buf_idx++;
s->buf_idx %= s->nbfrags;
s->buf = s->buffers + s->buf_idx;
//先将环形缓冲区索引号加一,并取模音频缓冲区片个数(8),这样就得到了绕环递增的环形缓冲区序号。最后将当前缓冲区的指针指向环形缓冲区起始地址加上新的索引号,即指向了环形缓冲区中的下一组缓冲区地址。
if ((buffer - buffer0))
ret = buffer - buffer0;
return ret;
// 当count 长度的数据都写完后,就退出while 大循环。一开始定义了一个buffer0 的指针指向了buffer 的起始地址,在写数据的过程中,buffer 指针进行过向后移动,而buffer0 指针不变,buffer - buffer0 就得到了总共写入的数据长度,并将该长度值返回。
------------------------------------------------------------------------
// 马上来看一下创建DMA 缓冲区的函数audio_setup_buf:
static int audio_setup_buf(audio_stream_t * s)
if (s->buffers)
return -EBUSY;
// 若环形缓冲区指针s->buffers 不为空的话,则立即返回。表示已经创建过DMA 缓冲区了,则不再重复创建。
s->nbfrags = audio_nbfrags;
s->fragsize = audio_fragsize;
// 接着分别将音频缓冲区片数量和音频缓冲区片大小赋值给audio_stream_t 结构中相应的成员,
//s->nbfrags 音频缓冲区片数量为8,s->fragsize 音频缓冲区片大小为8192。
s->buffers = (audio_buf_t *)
kmalloc(sizeof(audio_buf_t) * s->nbfrags, GFP_KERNEL);
// 调用kmalloc 函数来申请环形缓冲区所需要的内存空间,返回值为所分配内存空间的起始地址,且为物理地址。
//再将audio_stream_t 结构的环形缓冲区指针s->buffers 指向转换为audio_buf_t 结构指针的内存起始地址(物理地址)。
//这里申请的只是结构体所需要的空间容量,而不是DMA 缓冲区。
if (!s->buffers)
goto err;
// 如果内存空间申请成功,则s->buffers 指针不为空,继续执行,否则直接跳到err 标号处执行。
memset(s->buffers, 0, sizeof(audio_buf_t) * s->nbfrags);
// 调用memset 函数对刚才分配的那块内存空间进行清零操作。
for (frag = 0; frag < s->nbfrags; frag++)
// 接着进入一个for 大循环,对连续的s->nbfrags 个音频缓冲区片进行操作。
{
audio_buf_t *b = &s->buffers[frag];
// 首先又定义了一个audio_buf_t 结构的指针变量指向audio_stream_t 结构变量的各个缓冲区地址s->buffers[frag],其中frag 从0~8,即8个缓冲区组成一个环形缓冲区。
if (!dmasize) {
dmasize = (s->nbfrags - frag) * s->fragsize;
// 接着进行判断,如果dmasize 为0,则继续执行。这里一开始就定义了dmasize 为0。一开始,先将dmasize 赋值为所需要的最大的缓冲区空间,即(8-0)*8192。
do {
dmabuf = consistent_alloc(GFP_KERNEL|GFP_DMA,
dmasize, &dmaphys);
if (!dmabuf)
dmasize -= s->fragsize;
} while (!dmabuf && dmasize);
// 下面又进入一个do while 循环,调用consistent_alloc 函数来进行内存分配,该函数在《LCD驱动程序分析》一文中有过详细分析。通过调用该函数来分配先前dmasize 大小的内存空间(所需要的最大的缓冲区空间)。返回两个值,一个是dmabuf,为所分配内存空间的起始地址,为虚拟地址;另一个是dmaphys,也为所分配内存空间的起始地址,为物理地址。
// 如果返回的dmabuf 值为0,则表示内存没有申请成功,那么要分配的内存空间dmasize 就需要进行减少,减去一个缓冲区片大小,再调用consistent_alloc 函数进行内存分配,知道分配成功或dmasize 为0 才退出循环。
if (!dmabuf)
goto err;
// 如果最后dmabuf 值还为0,则表示内存没有申请成功,直接跳到err 标号处执行。
b->master = dmasize;
}
// 接着把所分配的内存大小赋值给b->master 表示内存大小的结构参数。
b->start = dmabuf;
b->dma_addr = dmaphys;
// 将所分配的内存空间起始地址的虚拟地址赋值给b->start 这个虚拟地址指针,物理地址赋值给b->dma_addr 这个DMA 缓冲区地址。
sema_init(&b->sem, 1);
// 调用sema_init 函数来初始化一个信号量,将信号量的初值设置为1。
//在/kernel/include/asm-arm/semaphore.h 文件中:
static inline void sema_init(struct semaphore *sem, int val)
//关于该函数和相关函数的说明可以参考一篇《Linux内核的同步机制》的文档。
dmabuf += s->fragsize;
dmaphys += s->fragsize;
dmasize -= s->fragsize;
}
// 在for 大循环的最后,将所分配内存起始地址的虚拟地址和物理地址都加上音频缓冲区片的大小,
//而总的缓冲区空间大小是减去音频缓冲区片的大小。前面两个参数都将作为下一个缓冲区地址audio_buf_t
//结构中的虚拟地址指针和DMA 缓冲区地址的参数。
// 如果dmasize 不为0 的话,在进入下一次循环时,就不会进入do while 循环进行内存空间的分配了。
//但是如果第一次没有分配到8 个音频缓冲区片大小的内存空间,比如只分配到4 个音频缓冲区片大小的内存空间,
//则进入第5 次循环时,dmasize 为0 了,那么就会再次进入do while 循环进行内存空间的分配,
//不过分配的为剩下的4 个音频缓冲区片大小的内存空间。
//这个函数巧妙的解决了万一一次分配不到连续的8 个音频缓冲区片大小的内存空间,就会按几次来分配较小的连续的内存空间了。
// 其中b->master 参数只有第0个缓冲区地址有值,为总的缓冲区空间大小,其余缓冲区地址的b->master 都为0。
s->buf_idx = 0;
s->buf = &s->buffers[0];
return 0;
// 将环形缓冲区索引号设为0,将当前缓冲区指针指向环形缓冲区的第0个缓冲区地址,然后返回0。
err:
audio_clear_buf(s);
return -ENOMEM;
// 如果程序跳转到err 标号处,则执行audio_clear_buf 函数来清空输出音频DMA 缓冲区,然后返回出错信息。
------------------------------------------------------------------------
// 分析完了放音的写设备函数,再来看一下录音的读设备函数smdk2410_audio_read:
static ssize_t smdk2410_audio_read(struct file *file, char *buffer,
size_t count, loff_t * ppos)
audio_stream_t *s = &input_stream;
// 该函数首先又定义了一个audio_stream_t 结构的指针变量指向输入音频缓冲区。
if (ppos != &file->f_pos)
return -ESPIPE;
// 然后判断如果表示文件当前位置的参数ppos 不等于该文件file 结构里的file->f_pos 文件位置,则返回退出。
//但实际上ppos 本来就是file->f_pos 的值,所以这一步一般不会出现。
if (!s->buffers) {
int i;
if (audio_setup_buf(s))
return -ENOMEM;
for (i = 0; i < s->nbfrags; i++) {
audio_buf_t *b = s->buf;
down(&b->sem);
s3c2410_dma_queue_buffer(s->dma_ch, (void *) b,
b->dma_addr, s->fragsize, DMA_BUF_RD);
NEXT_BUF(s, buf);
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -