copy_to_user是编写驱动时经常遇到的函数这个函数的主要作用就是从内核空间拷贝一块儿数据到用户空间。它有如下三个参数:
To 目标地址,这个地址是用户空间的地址;
From 源地址,这个地址是内核空间的地址;
N 将要拷贝的数据的字节数。
如果数据拷贝成功,则返回零;否则,返回没有拷贝成功的数据字节数。
让人比较头疼的是平时字符型驱动写习惯了,在read函数中执行完此函数就直接返回了。一直以为用户空间调用read的返回值就是copy_to_user()函数的返回值,今天在看音频驱动的时候发现它的一个用法,一直认为是错的,到现在头脑才清醒一点:
copy_to_user()执行完只是表示从内核空间复制了一块数据到用户空间,但是read函数可能还没有执行结束呢。
下面是我看到的代码,顺便将4020音频的读写过程梳理一下:
static ssize_t sep4020_audio_read(struct file *file, char *buffer,size_t count, loff_t * ppos)
{
#ifdef DEBUG
printk("audio_read: count=%d\n", count);
#endif
const char *buffer0 = buffer;
audio_stream_t *s = &input_stream;
int chunksize, ret = 0;
if (!s->buffers) //创建读缓冲空间,由于input_stream为全局变量,此缓冲空间只创建一次
{
int i;
if (audio_setup_buf(s)) //此函数创建了8个音频缓冲区片,每个大小16字节,但是在写函数中每个缓冲区片却为8192字节。
return -ENOMEM;
for (i = 0; i < s->nbfrags; i++) //s->nbfrags为8,此循环主要先用获得的数据将8个缓冲区片填满
{
audio_buf_t *b = s->buf; //先指向第一个缓冲区片
down(&b->sem);
while(read_mark) //判断上一次的DMA传输完成
{
if(((*(volatile unsigned long*)DMAC_INTTCSTATUS_V) & 0x2 ) == 0x2) break;
}
/*清除要用的dma通道(dma1)上的传输完成中断寄存器*/
*(volatile unsigned long*)(DMAC_INTTCCLEAR_V) = 0x2;
*(volatile unsigned long*)(DMAC_INTTCCLEAR_V) = 0x0;
read_mark = 1;
*(volatile unsigned long*)DMAC_C1CONTROL_V = ((AUDIO_FRAGSIZE_DEFAULT_READ>>2)<<14) + (1<<12) + (2<<9) + (2<<6) + (3<<3) + 3;
*(volatile unsigned long*)DMAC_C1SRCADDR_V = I2S_DATA;
*(volatile unsigned long*)DMAC_C1DESTADDR_V = b->dma_addr;
*(volatile unsigned long*)DMAC_C1CONFIGURATION_V = 0x20d ;
up(&b->sem); //this should be placed in the interrupt recall function
wake_up(&b->sem.wait);
b->size = s->fragsize; //此处b->size 为16了
NEXT_BUF(s, buf); //获得下一个缓冲区片
}
}
while (count > 0) //我们现有的一个应用程序count初始值为4096,这个不重要
{
audio_buf_t *b = s->buf;
/* Wait for a buffer to become full */
if (file->f_flags & O_NONBLOCK)
{
ret = -EAGAIN;
if (down_trylock(&b->sem))
break;
}
else
{
ret = -ERESTARTSYS;
//一般程序都是使用阻塞的,这句信号量的使用是为了保证单独缓冲片已可以再次使用,在完成了对一片缓冲片的使用之后,会up这片,来说明可以再次使用
if (down_interruptible(&b->sem))
break;
}
chunksize = b->size;
if (chunksize > count) chunksize = count; //如果chunksize 大于所要read'的值,则将chunksize 赋值为count
//一直以为这边执行结束了,应用程序中的read就已经执行完了一次,大错特错了,郁闷中。
if (copy_to_user(buffer, b->start + s->fragsize - b->size,chunksize)) {
{
up(&b->sem);//如果拷贝出错,释放信号量进行下次操作
return -EFAULT;
}
b->size -= chunksize; //正常情况下 b->size 应该为0了
buffer += chunksize;
count -= chunksize;
if (b->size > 0) //如果 b->size 不为0,说明缓冲区片中的数据没有一次性读完,则不能进行数据的传输,否则上一次的数据会丢失,此处释放信号量来进行下一次应用程序读取操作。
{
up(&b->sem);
break;
}
/* Make current buffer available for DMA again */
while(read_mark)
{
if(((*(volatile unsigned long*)DMAC_INTTCSTATUS_V) & 0x2 ) == 0x2) break;
}
/*清除要用的dma通道(dma1)上的传输完成中断寄存器*/
*(volatile unsigned long*)(DMAC_INTTCCLEAR_V) = 0x2;
*(volatile unsigned long*)(DMAC_INTTCCLEAR_V) = 0x0;
*(volatile unsigned long*)DMAC_C1CONTROL_V = ((AUDIO_FRAGSIZE_DEFAULT_READ>>2)<<14) + (1<<12) + (2<<9) +(2<<6) + (3<<3) + 3;
*(volatile unsigned long*)DMAC_C1SRCADDR_V = I2S_DATA;
*(volatile unsigned long*)DMAC_C1DESTADDR_V = b->dma_addr;
*(volatile unsigned long*)DMAC_C1CONFIGURATION_V = 0x20d ; //iis
b->size = s->fragsize; //dma开始传输后,重新将b->size 赋值为缓冲片去大小,16个字节。
up(&b->sem);
wake_up(&b->sem.wait);
NEXT_BUF(s, buf);
}
if ((buffer - buffer0)) //拷贝成功的总字节数
ret = buffer - buffer0;
return ret;
}
写函数:
static ssize_t sep4020_audio_write(struct file *file, const char *buffer,size_t count, loff_t * ppos)
{
#ifdef DEBUG
printk("sep4020_audio_write! count=%d\n", count);
#endif
const char *buffer0 = buffer;
audio_stream_t *s = &output_stream;
int chunksize, ret = 0;
switch (file->f_flags & O_ACCMODE)
{
case O_WRONLY:
break;
case O_RDWR: //sep4020 不支持O_RDWR
default:
return -EPERM;
}
if (!s->buffers && audio_setup_buf(s)) //因为s->buffers 在open时即已经分配好,所以此处条件为假
return -ENOMEM;
count &= ~0x03; //DMA data must be 4 byte allied
while (count > 0)
{
audio_buf_t *b = s->buf; //定义结构的指针变量b,指向当前缓冲区指针
if (file->f_flags & O_NONBLOCK)
{
ret = -EAGAIN;
if (down_trylock(&b->sem))
break;
}
else
{
ret = -ERESTARTSYS;
//一般程序都是使用阻塞的,这句信号量的使用是为了保证单独缓冲片已可以再次使用,在完成了对一片缓冲片的使用之后,会up这片,来说明可以再次使用
if (down_interruptible(&b->sem))
break;
}
if (audio_channels == 2) //双声道,s->fragsize 为 8192
{
chunksize = s->fragsize - b->size; //b->size缓冲区地址的偏移量 一开始这个值为0
if (chunksize > count) //如果写入的数据小于一个chunksize ,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;
}
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;
}
buffer += chunksize;
count -= chunksize;
//如果这次填充缓冲片没有填满,会释放这一片的信号量,继续对这一片进行操作,直到填满开的缓冲片大小为止,保证了缓冲片内不会有空白区间,这对音频是致命的。
if (b->size < s->fragsize)
{
up(&b->sem);
break;
}
while(write_mark) //第一次不用判断DMA是否就绪
{
if(((*(volatile unsigned long*)DMAC_INTTCSTATUS_V) & 0x2 ) == 0x2) break;
}
/*清除要用的dma通道(dma1)上的传输完成中断寄存器*/
*(volatile unsigned long*)(DMAC_INTTCCLEAR_V) = 0x2;
*(volatile unsigned long*)(DMAC_INTTCCLEAR_V) = 0x0;
write_mark = 1;
*(volatile unsigned long*)DMAC_C1CONTROL_V = ((AUDIO_FRAGSIZE_DEFAULT>>2)<<14) + (1<<12) + (2<<9) + (2<<6) + (3<<3) + 3;
*(volatile unsigned long*)DMAC_C1SRCADDR_V = b->dma_addr;
*(volatile unsigned long*)DMAC_C1DESTADDR_V = I2S_DATA ;
*(volatile unsigned long*)DMAC_C1CONFIGURATION_V = 0x200b ; //iis
up(&b->sem); //此处应该在DMA中断处理函数完成,但是dma搬运的速度远远小于从上层拷贝到缓冲片的速度,也就是说我在准备好下一片缓冲片的时候,dma肯定还没有搬好,那我只需要等待dma搬好,并把下一片的缓冲数据给他,让他继续搬运。
wake_up(&b->sem.wait);
#ifdef DEBUG
printk("dma1 done! b->size=%d\n",b->size);
#endif
b->size = 0; //一个缓冲片填满之后,重新将 b->size 赋值为0
NEXT_BUF(s, buf); //准备下一个缓冲片区
#ifdef DEBUG
printk("next buf!\n");
#endif
}
if ((buffer - buffer0))
ret = buffer - buffer0;
DPRINTK("audio_write : end count=%d\n\n", ret);
return ret; //返回传输成功的字节数
}
该补补自己的知识了。
参考文章:
http://blogold.chinaunix.net/u3/99423/showart_1972927.html
http://www.unixresources.net/linux/clf/linuxK/archive/00/00/47/67/476746.html